Compare commits

...

73 Commits

Author SHA1 Message Date
Michael Albert dd71a08fe9 Preinstall icons-material to avoid timeout
Change-Id: Ie4b5fa3a923cac19a3f2eeae5f6c06245d0b8cc2
2023-01-25 12:05:56 +01:00
Michael Albert 137a5828df Raise yarn install timeout limit to avoid build errors
@mui/icons-material needs much time to install and raises the overall yarn install time to about 150 seconds

Change-Id: Ibe9808554c97d0991a336d3c7588502af8505a3f
2023-01-25 09:13:44 +01:00
Michael Albert c6ed88176d Update yarn.lock
Change-Id: I0af3f9b98bf4ea2c7d016c650fa4b28567a414be
2023-01-24 22:22:11 +01:00
Michael Albert b3a611e7ad Create test-docker-image.yml
Change-Id: I2a350b2a33233c9a271739643acc0887558cc52e
2023-01-24 22:16:58 +01:00
Michael Albert de03e23524 Fix coding style of french translation
Change-Id: Id5e2a5b2fefcbff4cefbd577cc9fae5315be9cb6
2023-01-24 21:50:59 +01:00
Charlie Calendre a200a8932b feat: add french translations (#271)
Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
2023-01-24 21:41:56 +01:00
Dirk Klimpel 41656748bb Add dependencies @mui/icons-material to package (#242)
* Add dependencies `@mui/icons-material` to package

* Replace MUI icons v4 with v5

* Replace more `@material-ui/core` with `@mui/material`

* consolidate `Dialog`, `DialogContent`, `DialogContentText` and
`DialogTitle`

* update `alpha` `Container` and `useMediaQuery`
2023-01-24 21:27:02 +01:00
Dirk Klimpel 42a4decc2a Remove not needed translate (#240) 2023-01-24 21:25:20 +01:00
Dirk Klimpel 74f77e6988 Replace ({ record }) with useRecordContext() (#236)
* replace `({ record })` with `useRecordContext()`

* code style

Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
2023-01-24 16:36:08 +01:00
Dirk Klimpel 8501f19a03 Add admin API for destinations (#213)
* Add admin API for destinations

* Add rooms and connections to federation/destinations
2023-01-24 16:35:42 +01:00
Dirk Klimpel 6d30af9976 Update usage of useNotify hook (#234)
Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
2023-01-24 15:28:01 +01:00
Dirk Klimpel 2e59190bd0 Migrate useMutation to useDelete in devices.js (#225)
* Migrate `useMutation` to `useDelete` in `devices.js`

* Update deprecated `notify` call
2023-01-24 15:24:14 +01:00
Charlie Calendre a647c91f4f fix: prevent requests from failing (#272)
Encode userid to handle the case of a localpart with a slash
2023-01-24 15:18:09 +01:00
dependabot[bot] 21ba5c9862 Bump actions/setup-node from 2 to 3 (#316)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:07:29 +01:00
dependabot[bot] 0a701df5d2 Bump json5 from 1.0.1 to 1.0.2 (#302)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:04:49 +01:00
dependabot[bot] 347d1c3114 Bump loader-utils from 2.0.2 to 2.0.4 (#297)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:02:12 +01:00
dependabot[bot] 8982487eee Bump decode-uri-component from 0.2.0 to 0.2.2 (#299)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:58:44 +01:00
dependabot[bot] 7fa835f973 Bump @testing-library/jest-dom from 5.16.1 to 5.16.5 (#314)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.16.1 to 5.16.5.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.16.1...v5.16.5)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:53:32 +01:00
Dirk Klimpel 5767817733 Set dist for Travis CI (#300) 2023-01-24 09:51:19 +01:00
dependabot[bot] e43f732bde Bump docker/setup-buildx-action from 1 to 2 (#318)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:50:09 +01:00
dependabot[bot] c0f4a92f1a Bump softprops/action-gh-release from 0.1.5 to 0.1.15 (#317)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 0.1.5 to 0.1.15.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/b7e450da2a4b4cb4bfbae528f788167786cfcedf...de2c0eb89ae2a093876385947365aca7b0e5f844)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:47:19 +01:00
dependabot[bot] e70c680e8a Bump eslint from 7.32.0 to 8.32.0 (#315)
Bumps [eslint](https://github.com/eslint/eslint) from 7.32.0 to 8.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.32.0...v8.32.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:35:54 +01:00
Dirk Klimpel 3b4da1d3c2 Update travis-ci URLs (#311) 2023-01-24 09:29:59 +01:00
Dirk Klimpel 3b2aa776eb Fix broken CI / lint (#320) 2023-01-24 09:29:14 +01:00
Michael Albert ee8050f697 Add args to docker-compose as suggested in #285
Change-Id: I67083d86ea45c15b02f8db54a1649dbb013c3d50
2023-01-16 20:27:22 +01:00
Sebastian Wagner ca5fde9190 doc README: fix PUBLIC_URL in docker-compose example (#289)
PUBLIC_URL must not be enclosed in quotation marks, see Awesome-Technologies/synapse-admin#286
- add a note on this in the example
- fix the example itself

fixes Awesome-Technologies/synapse-admin#286
2023-01-16 20:20:02 +01:00
Przemysław Romanik 561daf7737 Add session logout warning below password input (#212)
Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
2023-01-16 20:18:21 +01:00
dependabot[bot] f2526dc00e Bump @testing-library/user-event from 13.5.0 to 14.4.3 (#309)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.5.0 to 14.4.3.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.5.0...v14.4.3)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:13:59 +01:00
dependabot[bot] 3cce1a61d3 Bump eslint-plugin-prettier from 3.4.1 to 4.2.1 (#310)
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.4.1 to 4.2.1.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/commits/v4.2.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:13:41 +01:00
dependabot[bot] af3b5439a5 Bump actions/checkout from 2 to 3 (#308)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:13:25 +01:00
dependabot[bot] 59afa49be0 Bump docker/login-action from 1 to 2 (#307)
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:13:12 +01:00
dependabot[bot] bfd38f50ea Bump JamesIves/github-pages-deploy-action from 4.1.5 to 4.4.1 (#304)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/JamesIves/github-pages-deploy-action) from 4.1.5 to 4.4.1.
- [Release notes](https://github.com/JamesIves/github-pages-deploy-action/releases)
- [Commits](https://github.com/JamesIves/github-pages-deploy-action/compare/4.1.5...v4.4.1)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:12:59 +01:00
dependabot[bot] 37ed5c4156 Bump docker/setup-qemu-action from 1 to 2 (#306)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 2.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 20:06:09 +01:00
dependabot[bot] 2eb16eb747 Bump docker/build-push-action from 2 to 3 (#305)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 3.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 19:58:38 +01:00
Dirk Klimpel bd6b6eef85 Use delete room API v2 (delete room in background) (#211)
Co-authored-by: Michael Albert <37796947+awesome-michael@users.noreply.github.com>
2023-01-16 19:53:17 +01:00
Dirk Klimpel 43f1b82d33 Add dependabot.yml config (#292) 2023-01-16 19:52:25 +01:00
Michael Albert 24afbd2953 Fix build environment
Change-Id: I1e32a0eeaaa0e6b8c8d4845183e7d37320a4eeae
2023-01-16 13:42:16 +01:00
Dirk Klimpel a74a6166a3 Add some status badges to README.md (#291) 2023-01-16 12:53:38 +01:00
Dirk Klimpel e23e9ccd82 Create UserEditActions on user page (#222)
* Create `UserEditActions` on user page

* Fix crash with crashes `undefined`
2023-01-16 12:52:00 +01:00
Dirk Klimpel e787b0a940 Add edit user_type to user (#209) 2023-01-16 12:37:30 +01:00
Dirk Klimpel 67066a1ba7 Bump react-scripts to ^5.0.1 (#280) 2022-09-07 09:20:25 +02:00
dklimpel 08a7d5c0f6 Bump GHA to NodeJS v16 2022-09-07 08:49:02 +02:00
dependabot[bot] 472b13ec26 Bump terser from 4.8.0 to 4.8.1
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-07 08:48:17 +02:00
dependabot[bot] b901c9e0ff Bump eventsource from 1.1.0 to 1.1.1
Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/EventSource/eventsource/releases)
- [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md)
- [Commits](https://github.com/EventSource/eventsource/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: eventsource
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-07 08:48:04 +02:00
Dirk Klimpel 0b153ddcbb Update README.md
Co-authored-by: Dominik Fuchß <develop@fuchss.org>
2022-05-23 14:07:49 +02:00
Dirk Klimpel 784c284723 Add note to GH pages 2022-05-23 14:07:49 +02:00
dependabot[bot] e5f73ea8b4 Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 14:07:01 +02:00
dependabot[bot] 96551259c5 Bump cross-fetch from 3.1.4 to 3.1.5
Bumps [cross-fetch](https://github.com/lquixada/cross-fetch) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/lquixada/cross-fetch/releases)
- [Commits](https://github.com/lquixada/cross-fetch/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: cross-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 14:06:50 +02:00
dependabot[bot] b2fa533ef0 Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 14:06:40 +02:00
Leon Schmidt 7aa0f9f50d Make PUBLIC_URL and REACT_APP_SERVER configurable (#266)
* Adjust Dockerfile to include args in build process
* Adjust README.md
2022-05-23 14:06:04 +02:00
Dirk Klimpel 5aa90c25f7 Add path to rooms tab "members" (#232) 2022-03-22 19:11:12 +01:00
dependabot[bot] 38d58db08d Bump url-parse from 1.5.7 to 1.5.10 (#253)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-22 18:17:18 +01:00
Dirk Klimpel e637df232a Upgrade react-admin to ^3.19.7 (#241) 2022-03-22 18:13:01 +01:00
Dirk Klimpel d97c633cd0 Remove not needed {" "} from EventReports (#235) 2022-03-22 17:59:48 +01:00
Dirk Klimpel b02396c61f disableReordering in users' SimpleFormIterator (#219) 2022-03-22 17:58:28 +01:00
Michael Albert 95de50b925 Bump version to 0.8.5
Change-Id: I8c8f0af01d693bb76dbf706a92fdd4dfa6ba9a8b
2022-02-17 20:56:27 +01:00
dependabot[bot] 1a150a10fd Bump url-parse from 1.5.3 to 1.5.7 (#244)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.7.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.7)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 20:53:56 +01:00
Dirk Klimpel 158e7dbe98 Move date format to a constant in EventReports.js (#237) 2022-02-17 20:52:13 +01:00
Dirk Klimpel a642f11503 Fix typo notification in german language file (#233) 2022-02-17 20:51:42 +01:00
Dirk Klimpel 888a3f001b Move date format to a constant in rooms.js (#238) 2022-02-17 20:51:11 +01:00
Dirk Klimpel 4b0845bee8 Move date format to a constant in users.js (#223)
* Move date format to a constant in `users.js`

* yarn prettier
2022-02-17 20:50:38 +01:00
Dirk Klimpel f449e3277a Replace deprecated fade in devices.js (#220) 2022-02-17 20:49:34 +01:00
Dirk Klimpel 3303f253b4 Replace deprecated fade in media.js (#221) 2022-02-17 20:49:19 +01:00
dependabot[bot] 0250954ee7 Bump follow-redirects from 1.14.7 to 1.14.8 (#243)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 20:45:37 +01:00
Dirk Klimpel efed8b2774 Add token based registration creation and management (#200)
* Add token based registration creation and management

* yarn fix

* Apply suggestions from code review

Remove empty line

* move date to `const date_format`
2022-02-17 20:45:21 +01:00
Nya Candy c891afa611 feat: support SSO login (#196)
* feat: support SSO login

* fix: lint

* fix: add back homeserver force protection

* fix: add back login notice

* fix: simplify login options
2022-02-17 20:24:46 +01:00
Michael Albert 38541b8f02 Fix english translation for room deletion
Change-Id: I5bb02e64832902a79379d66c77d3128169d3fca8
2022-02-17 20:15:49 +01:00
Dirk Klimpel 0f4c382c18 Remove not needed translation backtolist (#208) 2022-01-31 17:50:14 +01:00
Dirk Klimpel b90d4ef00f Add more headlines for installation to README (#216) 2022-01-31 17:39:18 +01:00
Dominik Fuchß 3fb33facc5 GitHub Pages Deploy (#189)
* Create edge_ghpage.yml

Added Build and Deploy Edge version to GH Pages

* Update edge_ghpage.yml

Added missing node setup

* Update edge_ghpage.yml

Restrict building of GH Pages to main / master branch & workflow dispatch

* Update .github/workflows/edge_ghpage.yml

Co-authored-by: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>

* Update .github/workflows/edge_ghpage.yml

Co-authored-by: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>

* Update .github/workflows/edge_ghpage.yml

Co-authored-by: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>

* Update .github/workflows/edge_ghpage.yml

Co-authored-by: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>

Co-authored-by: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com>
2022-01-31 17:38:34 +01:00
Dirk Klimpel c4a68ff1d5 Fix typo in .prettierrc (#224) 2022-01-31 17:24:37 +01:00
dependabot[bot] c4f0fa48ec Bump nanoid from 3.1.30 to 3.2.0 (#229)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 17:20:39 +01:00
dependabot[bot] 26ed63d65e Bump follow-redirects from 1.14.6 to 1.14.7 (#227)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.6 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.6...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 17:20:29 +01:00
32 changed files with 6861 additions and 7503 deletions
+20
View File
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
# Major updates for react-admin have breaking changes
- dependency-name: "react-admin"
update-types: ["version-update:semver-major"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+3 -3
View File
@@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Setup node - name: Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 16
- name: Install dependencies - name: Install dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
- name: Run tests - name: Run tests
+5 -5
View File
@@ -17,13 +17,13 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -43,7 +43,7 @@ jobs:
esac esac
echo "::set-output name=tag::$tag" echo "::set-output name=tag::$tag"
- name: Build and Push Tag - name: Build and Push Tag
uses: docker/build-push-action@v2 uses: docker/build-push-action@v3
with: with:
context: . context: .
push: true push: true
+26
View File
@@ -0,0 +1,26 @@
name: Build and Deploy Edge version to GH Pages
on:
workflow_dispatch:
push:
branches:
- main
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "16"
- name: Install and Build 🔧
run: |
yarn install
yarn build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: build
+4 -4
View File
@@ -13,10 +13,10 @@ jobs:
packages: write packages: write
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v3
with: with:
node-version: "14" node-version: "16"
- run: yarn install - run: yarn install
- run: yarn build - run: yarn build
- run: | - run: |
@@ -24,7 +24,7 @@ jobs:
mkdir -p dist mkdir -p dist
cp -r build synapse-admin-$version cp -r build synapse-admin-$version
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
- uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
with: with:
files: dist/*.tar.gz files: dist/*.tar.gz
env: env:
+51
View 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@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
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@v3
with:
context: .
push: false
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
platforms: linux/amd64,linux/arm64
+2 -2
View File
@@ -6,6 +6,6 @@
"singleQuote": false, "singleQuote": false,
"trailingComma": "es5", "trailingComma": "es5",
"bracketSpacing": true, "bracketSpacing": true,
"jsxBracketSameLine": false, "bracketSameLine": false,
"arrowParens": "avoid", "arrowParens": "avoid"
} }
+2 -1
View File
@@ -1,5 +1,6 @@
dist: focal
language: node_js language: node_js
node_js: node_js:
- lts/* - 17
cache: yarn cache: yarn
+5 -1
View File
@@ -1,11 +1,15 @@
# Builder # Builder
FROM node:lts as builder FROM node:lts as builder
ARG PUBLIC_URL=/
ARG REACT_APP_SERVER
WORKDIR /src WORKDIR /src
COPY . /src COPY . /src
RUN yarn --network-timeout 500000 add @mui/icons-material
RUN yarn --network-timeout=100000 install RUN yarn --network-timeout=100000 install
RUN yarn build RUN PUBLIC_URL=$PUBLIC_URL REACT_APP_SERVER=$REACT_APP_SERVER yarn build
# App # App
+32 -9
View File
@@ -1,17 +1,27 @@
[![Build Status](https://travis-ci.org/Awesome-Technologies/synapse-admin.svg?branch=master)](https://travis-ci.org/Awesome-Technologies/synapse-admin) [![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
[![Build Status](https://api.travis-ci.com/Awesome-Technologies/synapse-admin.svg?branch=master)](https://app.travis-ci.com/github/Awesome-Technologies/synapse-admin)
[![build-test](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml) [![build-test](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml)
[![gh-pages](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/edge_ghpage.yml/badge.svg)](https://awesome-technologies.github.io/synapse-admin/)
[![docker-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/docker-release.yml/badge.svg)](https://hub.docker.com/r/awesometechnologies/synapse-admin)
[![github-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/github-release.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/releases)
# Synapse admin ui # Synapse admin ui
This project is built using [react-admin](https://marmelab.com/react-admin/). This project is built using [react-admin](https://marmelab.com/react-admin/).
It needs at least Synapse v1.41.0 for all functions to work as expected! ## Usage
### Supported Synapse
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.52.0 for all functions to work as expected!
You get your server version with the request `/_synapse/admin/v1/server_version`. You get your server version with the request `/_synapse/admin/v1/server_version`.
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html). See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
After entering the URL on the login page of synapse-admin the server version appears below the input field. After entering the URL on the login page of synapse-admin the server version appears below the input field.
### Prerequisites
You need access to the following endpoints: You need access to the following endpoints:
- `/_matrix` - `/_matrix`
@@ -19,15 +29,25 @@ You need access to the following endpoints:
See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints) See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints)
## Step-By-Step install: ### Use without install
You can use the current version of Synapse Admin without own installation direct
via [GitHub Pages](https://awesome-technologies.github.io/synapse-admin/).
**Note:**
If you want to use the deployment, you have to make sure that the admin endpoints (`/_synapse/admin`) are accessible for your browser.
**Remember: You have no need to expose these endpoints to the internet but to your network.**
If you want your own deployment, follow the [Step-By-Step Install Guide](#step-by-step-install) below.
### Step-By-Step install
You have three options: You have three options:
1. Download the tarball and serve with any webserver 1. [Download the tarball and serve with any webserver](#steps-for-1)
2. Download the source code from github and run using nodejs 2. [Download the source code from github and run using nodejs](#steps-for-2)
3. Run the Docker container 3. [Run the Docker container](#steps-for-3)
Steps for 1): #### Steps for 1)
- make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do) - make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do)
- configure a vhost for synapse admin on your webserver - configure a vhost for synapse admin on your webserver
@@ -36,7 +56,7 @@ Steps for 1):
- move or symlink the `synapse-admin-x.x.x` into your vhosts root dir - move or symlink the `synapse-admin-x.x.x` into your vhosts root dir
- open the url of the vhost in your browser - open the url of the vhost in your browser
Steps for 2): #### Steps for 2)
- make sure you have installed the following: git, yarn, nodejs - make sure you have installed the following: git, yarn, nodejs
- download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git` - download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git`
@@ -49,7 +69,7 @@ Either you define it at startup (e.g. `REACT_APP_SERVER=https://yourmatrixserver
or by editing it in the [.env](.env) file. See also the or by editing it in the [.env](.env) file. See also the
[documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/). [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`
@@ -66,6 +86,9 @@ Steps for 3):
context: https://github.com/Awesome-Technologies/synapse-admin.git context: https://github.com/Awesome-Technologies/synapse-admin.git
# args: # args:
# - NODE_OPTIONS="--max_old_space_size=1024" # - NODE_OPTIONS="--max_old_space_size=1024"
# # see #266, PUBLIC_URL must be without surrounding quotation marks
# - PUBLIC_URL=/synapse-admin
# - REACT_APP_SERVER="https://matrix.example.com"
ports: ports:
- "8080:80" - "8080:80"
restart: unless-stopped restart: unless-stopped
+6 -1
View File
@@ -12,10 +12,15 @@ services:
# replace the context definition with this: # replace the context definition with this:
# context: https://github.com/Awesome-Technologies/synapse-admin.git # context: https://github.com/Awesome-Technologies/synapse-admin.git
# args:
# if you're building on an architecture other than amd64, make sure # if you're building on an architecture other than amd64, make sure
# to define a maximum ram for node. otherwise the build will fail. # to define a maximum ram for node. otherwise the build will fail.
# args:
# - NODE_OPTIONS="--max_old_space_size=1024" # - NODE_OPTIONS="--max_old_space_size=1024"
# default is /
# - PUBLIC_URL=/synapse-admin
# You can use a fixed homeserver, so that the user can no longer
# define it himself
# - REACT_APP_SERVER="https://matrix.example.com"
ports: ports:
- "8080:80" - "8080:80"
restart: unless-stopped restart: unless-stopped
+13 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "synapse-admin", "name": "synapse-admin",
"version": "0.8.4", "version": "0.8.5",
"description": "Admin GUI for the Matrix.org server Synapse", "description": "Admin GUI for the Matrix.org server Synapse",
"author": "Awesome Technologies Innovationslabor GmbH", "author": "Awesome Technologies Innovationslabor GmbH",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -10,25 +10,31 @@
"url": "https://github.com/Awesome-Technologies/synapse-admin" "url": "https://github.com/Awesome-Technologies/synapse-admin"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.1.1", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^11.2.6", "@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.8", "@testing-library/user-event": "^14.4.3",
"eslint": "^7.25.0", "eslint": "^8.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.1.2", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"prettier": "^2.2.0", "prettier": "^2.2.0",
"ra-test": "^3.15.0" "ra-test": "^3.15.0"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.3.1",
"@mui/material": "^5.4.0",
"papaparse": "^5.2.0", "papaparse": "^5.2.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"ra-language-chinese": "^2.0.10", "ra-language-chinese": "^2.0.10",
"ra-language-french": "^4.2.0",
"ra-language-german": "^3.13.4", "ra-language-german": "^3.13.4",
"react": "^17.0.0", "react": "^17.0.0",
"react-admin": "^3.15.0", "react-admin": "^3.19.7",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "^4.0.0" "react-scripts": "^5.0.1"
}, },
"scripts": { "scripts": {
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start", "start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
+29 -5
View File
@@ -7,23 +7,33 @@ import { UserList, UserCreate, UserEdit } from "./components/users";
import { RoomList, RoomShow } from "./components/rooms"; import { RoomList, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports"; import { ReportList, ReportShow } from "./components/EventReports";
import LoginPage from "./components/LoginPage"; import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group"; import ConfirmationNumberIcon from "@mui/icons-material/ConfirmationNumber";
import EqualizerIcon from "@material-ui/icons/Equalizer"; import CloudQueueIcon from "@mui/icons-material/CloudQueue";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import UserIcon from "@mui/icons-material/Group";
import { UserMediaStatsList } from "./components/statistics"; import { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@material-ui/icons/ViewList"; import RoomIcon from "@mui/icons-material/ViewList";
import ReportIcon from "@material-ui/icons/Warning"; import ReportIcon from "@mui/icons-material/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared"; import FolderSharedIcon from "@mui/icons-material/FolderShared";
import { DestinationList, DestinationShow } from "./components/destinations";
import { ImportFeature } from "./components/ImportFeature"; import { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
RegistrationTokenEdit,
RegistrationTokenList,
} from "./components/RegistrationTokens";
import { RoomDirectoryList } from "./components/RoomDirectory"; import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import germanMessages from "./i18n/de"; import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en"; import englishMessages from "./i18n/en";
import frenchMessages from "./i18n/fr";
import chineseMessages from "./i18n/zh"; import chineseMessages from "./i18n/zh";
// TODO: Can we use lazy loading together with browser locale? // TODO: Can we use lazy loading together with browser locale?
const messages = { const messages = {
de: germanMessages, de: germanMessages,
en: englishMessages, en: englishMessages,
fr: frenchMessages,
zh: chineseMessages, zh: chineseMessages,
}; };
const i18nProvider = polyglotI18nProvider( const i18nProvider = polyglotI18nProvider(
@@ -66,6 +76,19 @@ const App = () => (
list={RoomDirectoryList} list={RoomDirectoryList}
icon={FolderSharedIcon} icon={FolderSharedIcon}
/> />
<Resource
name="destinations"
list={DestinationList}
show={DestinationShow}
icon={CloudQueueIcon}
/>
<Resource
name="registration_tokens"
list={RegistrationTokenList}
create={RegistrationTokenCreate}
edit={RegistrationTokenEdit}
icon={ConfirmationNumberIcon}
/>
<Resource name="connections" /> <Resource name="connections" />
<Resource name="devices" /> <Resource name="devices" />
<Resource name="room_members" /> <Resource name="room_members" />
@@ -75,6 +98,7 @@ const App = () => (
<Resource name="servernotices" /> <Resource name="servernotices" />
<Resource name="forward_extremities" /> <Resource name="forward_extremities" />
<Resource name="room_state" /> <Resource name="room_state" />
<Resource name="destination_rooms" />
</Admin> </Admin>
); );
+14 -27
View File
@@ -12,8 +12,17 @@ import {
TextField, TextField,
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import PageviewIcon from "@material-ui/icons/Pageview"; import PageviewIcon from "@mui/icons-material/Pageview";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const ReportPagination = props => ( const ReportPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@@ -33,14 +42,7 @@ export const ReportShow = props => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<ReferenceField source="user_id" reference="users"> <ReferenceField source="user_id" reference="users">
@@ -68,18 +70,10 @@ export const ReportShow = props => {
icon={<PageviewIcon />} icon={<PageviewIcon />}
path="detail" path="detail"
> >
{" "}
<DateField <DateField
source="event_json.origin_server_ts" source="event_json.origin_server_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<ReferenceField source="sender" reference="users"> <ReferenceField source="sender" reference="users">
@@ -116,14 +110,7 @@ export const ReportList = ({ ...props }) => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={true} sortable={true}
/> />
<TextField sortable={false} source="user_id" /> <TextField sortable={false} source="user_id" />
+4 -4
View File
@@ -6,19 +6,19 @@ import {
Title, Title,
} from "react-admin"; } from "react-admin";
import { parse as parseCsv, unparse as unparseCsv } from "papaparse"; import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
import GetAppIcon from "@material-ui/icons/GetApp"; import GetAppIcon from "@mui/icons-material/GetApp";
import { import {
Button, Button,
Card, Card,
CardActions, CardActions,
CardContent, CardContent,
CardHeader, CardHeader,
FormControlLabel,
Checkbox, Checkbox,
Container,
FormControlLabel,
NativeSelect, NativeSelect,
} from "@material-ui/core"; } from "@mui/material";
import { useTranslate } from "ra-core"; import { useTranslate } from "ra-core";
import Container from "@material-ui/core/Container/Container";
import { generateRandomUser } from "./users"; import { generateRandomUser } from "./users";
const LOGGING = true; const LOGGING = true;
+91 -9
View File
@@ -21,9 +21,9 @@ import {
MenuItem, MenuItem,
Select, Select,
TextField, TextField,
} from "@material-ui/core"; } from "@mui/material";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import LockIcon from "@material-ui/icons/Lock"; import LockIcon from "@mui/icons-material/Lock";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
main: { main: {
@@ -78,11 +78,48 @@ const LoginPage = ({ theme }) => {
const login = useLogin(); const login = useLogin();
const notify = useNotify(); const notify = useNotify();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [supportPassAuth, setSupportPassAuth] = useState(true);
var locale = useLocale(); var locale = useLocale();
const setLocale = useSetLocale(); const setLocale = useSetLocale();
const translate = useTranslate(); const translate = useTranslate();
const base_url = localStorage.getItem("base_url"); const base_url = localStorage.getItem("base_url");
const cfg_base_url = process.env.REACT_APP_SERVER; const cfg_base_url = process.env.REACT_APP_SERVER;
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
if (loginToken) {
const ssoToken = loginToken[1];
console.log("SSO token is", ssoToken);
// Prevent further requests
window.history.replaceState(
{},
"",
window.location.href.replace(loginToken[0], "#").split("#")[0]
);
const baseUrl = localStorage.getItem("sso_base_url");
localStorage.removeItem("sso_base_url");
if (baseUrl) {
const auth = {
base_url: baseUrl,
username: null,
password: null,
loginToken: ssoToken,
};
console.log("Base URL is:", baseUrl);
console.log("SSO Token is:", ssoToken);
console.log("Let's try token login...");
login(auth).catch(error => {
alert(
typeof error === "string"
? error
: typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error"
: error.message
);
console.error(error);
});
}
}
const renderInput = ({ const renderInput = ({
meta: { touched, error } = {}, meta: { touched, error } = {},
@@ -132,11 +169,19 @@ const LoginPage = ({ theme }) => {
: typeof error === "undefined" || !error.message : typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error" ? "ra.auth.sign_in_error"
: error.message, : error.message,
"warning" { type: "warning" }
); );
}); });
}; };
const handleSSO = () => {
localStorage.setItem("sso_base_url", ssoBaseUrl);
const ssoFullUrl = `${ssoBaseUrl}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(
window.location.href
)}`;
window.location.href = ssoFullUrl;
};
const extractHomeServer = username => { const extractHomeServer = username => {
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/; const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
if (!username) return null; if (!username) return null;
@@ -188,6 +233,31 @@ const LoginPage = ({ theme }) => {
.catch(_ => { .catch(_ => {
setServerVersion(""); setServerVersion("");
}); });
// Set SSO Url
const authMethodUrl = `${formData.base_url}/_matrix/client/r0/login`;
let supportPass = false,
supportSSO = false;
fetchUtils
.fetchJson(authMethodUrl, { method: "GET" })
.then(({ json }) => {
json.flows.forEach(f => {
if (f.type === "m.login.password") {
supportPass = true;
} else if (f.type === "m.login.sso") {
supportSSO = true;
}
});
setSupportPassAuth(supportPass);
if (supportSSO) {
setSSOBaseUrl(formData.base_url);
} else {
setSSOBaseUrl("");
}
})
.catch(_ => {
setSSOBaseUrl("");
});
}, },
[formData.base_url] [formData.base_url]
); );
@@ -199,8 +269,8 @@ const LoginPage = ({ theme }) => {
autoFocus autoFocus
name="username" name="username"
component={renderInput} component={renderInput}
label={translate("ra.auth.username")} label="ra.auth.username"
disabled={loading} disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange} onBlur={handleUsernameChange}
resettable resettable
fullWidth fullWidth
@@ -210,9 +280,9 @@ const LoginPage = ({ theme }) => {
<PasswordInput <PasswordInput
name="password" name="password"
component={renderInput} component={renderInput}
label={translate("ra.auth.password")} label="ra.auth.password"
type="password" type="password"
disabled={loading} disabled={loading || !supportPassAuth}
resettable resettable
fullWidth fullWidth
/> />
@@ -221,7 +291,7 @@ const LoginPage = ({ theme }) => {
<TextInput <TextInput
name="base_url" name="base_url"
component={renderInput} component={renderInput}
label={translate("synapseadmin.auth.base_url")} label="synapseadmin.auth.base_url"
disabled={cfg_base_url || loading} disabled={cfg_base_url || loading}
resettable resettable
fullWidth fullWidth
@@ -261,6 +331,7 @@ const LoginPage = ({ theme }) => {
> >
<MenuItem value="de">Deutsch</MenuItem> <MenuItem value="de">Deutsch</MenuItem>
<MenuItem value="en">English</MenuItem> <MenuItem value="en">English</MenuItem>
<MenuItem value="fr">Français</MenuItem>
<MenuItem value="zh">简体中文</MenuItem> <MenuItem value="zh">简体中文</MenuItem>
</Select> </Select>
</div> </div>
@@ -273,13 +344,24 @@ const LoginPage = ({ theme }) => {
variant="contained" variant="contained"
type="submit" type="submit"
color="primary" color="primary"
disabled={loading} disabled={loading || !supportPassAuth}
className={classes.button} className={classes.button}
fullWidth fullWidth
> >
{loading && <CircularProgress size={25} thickness={2} />} {loading && <CircularProgress size={25} thickness={2} />}
{translate("ra.auth.sign_in")} {translate("ra.auth.sign_in")}
</Button> </Button>
<Button
variant="contained"
color="secondary"
onClick={handleSSO}
disabled={loading || ssoBaseUrl === ""}
className={classes.button}
fullWidth
>
{loading && <CircularProgress size={25} thickness={2} />}
{translate("synapseadmin.auth.sso_sign_in")}
</Button>
</CardActions> </CardActions>
</Card> </Card>
<Notification /> <Notification />
+3 -3
View File
@@ -1,10 +1,10 @@
// in src/Menu.js // in src/Menu.js
import * as React from "react"; import * as React from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useMediaQuery } from "@material-ui/core"; import { useMediaQuery } from "@mui/material";
import { MenuItemLink, getResources } from "react-admin"; import { MenuItemLink, getResources } from "react-admin";
import DefaultIcon from "@material-ui/icons/ViewList"; import DefaultIcon from "@mui/icons-material/ViewList";
import LabelIcon from "@material-ui/icons/Label"; import LabelIcon from "@mui/icons-material/Label";
const Menu = ({ onMenuClick, logout }) => { const Menu = ({ onMenuClick, logout }) => {
const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs")); const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs"));
+132
View File
@@ -0,0 +1,132 @@
import React from "react";
import {
BooleanInput,
Create,
Datagrid,
DateField,
DateTimeInput,
Edit,
Filter,
List,
maxValue,
number,
NumberField,
NumberInput,
regex,
SimpleForm,
TextInput,
TextField,
Toolbar,
} from "react-admin";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const validateToken = [regex(/^[A-Za-z0-9._~-]{0,64}$/)];
const validateUsesAllowed = [number()];
const validateLength = [number(), maxValue(64)];
const dateParser = v => {
const d = new Date(v);
if (isNaN(d)) return 0;
return d.getTime();
};
const dateFormatter = v => {
if (v === undefined || v === null) return;
const d = new Date(v);
const pad = "00";
const year = d.getFullYear().toString();
const month = (pad + (d.getMonth() + 1).toString()).slice(-2);
const day = (pad + d.getDate().toString()).slice(-2);
const hour = (pad + d.getHours().toString()).slice(-2);
const minute = (pad + d.getMinutes().toString()).slice(-2);
// target format yyyy-MM-ddThh:mm
return `${year}-${month}-${day}T${hour}:${minute}`;
};
const RegistrationTokenFilter = props => (
<Filter {...props}>
<BooleanInput source="valid" alwaysOn />
</Filter>
);
export const RegistrationTokenList = props => {
return (
<List
{...props}
filters={<RegistrationTokenFilter />}
filterDefaultValues={{ valid: true }}
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<TextField source="token" sortable={false} />
<NumberField source="uses_allowed" sortable={false} />
<NumberField source="pending" sortable={false} />
<NumberField source="completed" sortable={false} />
<DateField
source="expiry_time"
showTime
options={date_format}
sortable={false}
/>
</Datagrid>
</List>
);
};
export const RegistrationTokenCreate = props => (
<Create {...props}>
<SimpleForm redirect="list" toolbar={<Toolbar alwaysEnableSaveButton />}>
<TextInput
source="token"
autoComplete="off"
validate={validateToken}
resettable
/>
<NumberInput
source="length"
validate={validateLength}
helperText="resources.registration_tokens.helper.length"
step={1}
/>
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput source="expiry_time" parse={dateParser} />
</SimpleForm>
</Create>
);
export const RegistrationTokenEdit = props => {
return (
<Edit {...props}>
<SimpleForm>
<TextInput source="token" disabled />
<NumberInput source="pending" disabled />
<NumberInput source="completed" disabled />
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput
source="expiry_time"
parse={dateParser}
format={dateFormatter}
/>
</SimpleForm>
</Edit>
);
};
+19 -15
View File
@@ -1,8 +1,7 @@
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import Avatar from "@material-ui/core/Avatar"; import { Avatar, Chip } from "@mui/material";
import { Chip } from "@material-ui/core";
import { connect } from "react-redux"; import { connect } from "react-redux";
import FolderSharedIcon from "@material-ui/icons/FolderShared"; import FolderSharedIcon from "@mui/icons-material/FolderShared";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { import {
BooleanField, BooleanField,
@@ -19,6 +18,7 @@ import {
useMutation, useMutation,
useNotify, useNotify,
useTranslate, useTranslate,
useRecordContext,
useRefresh, useRefresh,
useUnselectAll, useUnselectAll,
} from "react-admin"; } from "react-admin";
@@ -87,7 +87,9 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
refresh(); refresh();
}, },
onFailure: error => onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"), notify("resources.room_directory.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -103,7 +105,8 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
); );
}; };
export const RoomDirectorySaveButton = ({ record }) => { export const RoomDirectorySaveButton = props => {
const record = useRecordContext();
const notify = useNotify(); const notify = useNotify();
const refresh = useRefresh(); const refresh = useRefresh();
const [create, { loading }] = useCreate("room_directory"); const [create, { loading }] = useCreate("room_directory");
@@ -119,7 +122,9 @@ export const RoomDirectorySaveButton = ({ record }) => {
refresh(); refresh();
}, },
onFailure: error => onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"), notify("resources.room_directory.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -177,7 +182,6 @@ export const FilterableRoomDirectoryList = ({
...props ...props
}) => { }) => {
const classes = useStyles(); const classes = useStyles();
const translate = useTranslate();
const filter = roomDirectoryFilters; const filter = roomDirectoryFilters;
const roomIdFilter = filter && filter.room_id ? true : false; const roomIdFilter = filter && filter.room_id ? true : false;
const topicFilter = filter && filter.topic ? true : false; const topicFilter = filter && filter.topic ? true : false;
@@ -196,48 +200,48 @@ export const FilterableRoomDirectoryList = ({
source="avatar_src" source="avatar_src"
sortable={false} sortable={false}
className={classes.small} className={classes.small}
label={translate("resources.rooms.fields.avatar")} label="resources.rooms.fields.avatar"
/> />
<TextField <TextField
source="name" source="name"
sortable={false} sortable={false}
label={translate("resources.rooms.fields.name")} label="resources.rooms.fields.name"
/> />
{roomIdFilter && ( {roomIdFilter && (
<TextField <TextField
source="room_id" source="room_id"
sortable={false} sortable={false}
label={translate("resources.rooms.fields.room_id")} label="resources.rooms.fields.room_id"
/> />
)} )}
{canonicalAliasFilter && ( {canonicalAliasFilter && (
<TextField <TextField
source="canonical_alias" source="canonical_alias"
sortable={false} sortable={false}
label={translate("resources.rooms.fields.canonical_alias")} label="resources.rooms.fields.canonical_alias"
/> />
)} )}
{topicFilter && ( {topicFilter && (
<TextField <TextField
source="topic" source="topic"
sortable={false} sortable={false}
label={translate("resources.rooms.fields.topic")} label="resources.rooms.fields.topic"
/> />
)} )}
<NumberField <NumberField
source="num_joined_members" source="num_joined_members"
sortable={false} sortable={false}
label={translate("resources.rooms.fields.joined_members")} label="resources.rooms.fields.joined_members"
/> />
<BooleanField <BooleanField
source="world_readable" source="world_readable"
sortable={false} sortable={false}
label={translate("resources.room_directory.fields.world_readable")} label="resources.room_directory.fields.world_readable"
/> />
<BooleanField <BooleanField
source="guest_can_join" source="guest_can_join"
sortable={false} sortable={false}
label={translate("resources.room_directory.fields.guest_can_join")} label="resources.room_directory.fields.guest_can_join"
/> />
</Datagrid> </Datagrid>
</List> </List>
+17 -9
View File
@@ -9,15 +9,18 @@ import {
useCreate, useCreate,
useMutation, useMutation,
useNotify, useNotify,
useRecordContext,
useTranslate, useTranslate,
useUnselectAll, useUnselectAll,
} from "react-admin"; } from "react-admin";
import MessageIcon from "@material-ui/icons/Message"; import MessageIcon from "@mui/icons-material/Message";
import IconCancel from "@material-ui/icons/Cancel"; import IconCancel from "@mui/icons-material/Cancel";
import Dialog from "@material-ui/core/Dialog"; import {
import DialogContent from "@material-ui/core/DialogContent"; Dialog,
import DialogContentText from "@material-ui/core/DialogContentText"; DialogContent,
import DialogTitle from "@material-ui/core/DialogTitle"; DialogContentText,
DialogTitle,
} from "@mui/material";
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => { const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
const translate = useTranslate(); const translate = useTranslate();
@@ -64,7 +67,8 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
); );
}; };
export const ServerNoticeButton = ({ record }) => { export const ServerNoticeButton = props => {
const record = useRecordContext();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const notify = useNotify(); const notify = useNotify();
const [create, { loading }] = useCreate("servernotices"); const [create, { loading }] = useCreate("servernotices");
@@ -81,7 +85,9 @@ export const ServerNoticeButton = ({ record }) => {
handleDialogClose(); handleDialogClose();
}, },
onFailure: () => onFailure: () =>
notify("resources.servernotices.action.send_failure", "error"), notify("resources.servernotices.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -127,7 +133,9 @@ export const ServerNoticeBulkButton = ({ selectedIds }) => {
handleDialogClose(); handleDialogClose();
}, },
onFailure: error => onFailure: error =>
notify("resources.servernotices.action.send_failure", "error"), notify("resources.servernotices.action.send_failure", {
type: "error",
}),
} }
); );
}; };
+185
View File
@@ -0,0 +1,185 @@
import React from "react";
import {
Button,
Datagrid,
DateField,
Filter,
List,
Pagination,
ReferenceField,
ReferenceManyField,
SearchInput,
Show,
Tab,
TabbedShowLayout,
TextField,
TopToolbar,
useRecordContext,
useDelete,
useNotify,
useRefresh,
useTranslate,
} from "react-admin";
import AutorenewIcon from "@material-ui/icons/Autorenew";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import ViewListIcon from "@material-ui/icons/ViewList";
const DestinationPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const destinationRowStyle = (record, index) => ({
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
});
const DestinationFilter = ({ ...props }) => {
return (
<Filter {...props}>
<SearchInput source="destination" alwaysOn />
</Filter>
);
};
export const DestinationReconnectButton = props => {
const record = useRecordContext();
const refresh = useRefresh();
const notify = useNotify();
const [handleReconnect, { isLoading }] = useDelete("destinations");
// Reconnect is not required if no error has occurred. (`failure_ts`)
if (!record || !record.failure_ts) return null;
const handleClick = e => {
// Prevents redirection to the detail page when clicking in the list
e.stopPropagation();
handleReconnect(
{ payload: { id: record.id } },
{
onSuccess: () => {
notify("ra.notification.updated", {
messageArgs: { smart_count: 1 },
});
refresh();
},
onFailure: () => {
notify("ra.message.error", { type: "error" });
},
}
);
};
return (
<Button
label="resources.destinations.action.reconnect"
onClick={handleClick}
disabled={isLoading}
>
<AutorenewIcon />
</Button>
);
};
const DestinationShowActions = props => (
<TopToolbar>
<DestinationReconnectButton />
</TopToolbar>
);
const DestinationTitle = props => {
const record = useRecordContext();
const translate = useTranslate();
return (
<span>
{translate("resources.destinations.name", 1)} {record.destination}
</span>
);
};
export const DestinationList = props => {
return (
<List
{...props}
filters={<DestinationFilter />}
pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }}
bulkActionButtons={false}
>
<Datagrid
rowStyle={destinationRowStyle}
rowClick={(id, basePath, record) => `${basePath}/${id}/show/rooms`}
>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
<DestinationReconnectButton />
</Datagrid>
</List>
);
};
export const DestinationShow = props => {
const translate = useTranslate();
return (
<Show
actions={<DestinationShowActions />}
title={<DestinationTitle />}
{...props}
>
<TabbedShowLayout>
<Tab label="status" icon={<ViewListIcon />}>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
</Tab>
<Tab
label={translate("resources.rooms.name", { smart_count: 2 })}
icon={<FolderSharedIcon />}
path="rooms"
>
<ReferenceManyField
reference="destination_rooms"
target="destination"
addLabel={false}
pagination={<DestinationPagination />}
perPage={50}
>
<Datagrid
style={{ width: "100%" }}
rowClick={(id, basePath, record) => `/rooms/${id}/show`}
>
<TextField
source="room_id"
label="resources.rooms.fields.room_id"
/>
<TextField source="stream_ordering" sortable={false} />
<ReferenceField
label="resources.rooms.fields.name"
source="id"
reference="rooms"
sortable={false}
link=""
>
<TextField source="name" sortable={false} />
</ReferenceField>
</Datagrid>
</ReferenceManyField>
</Tab>
</TabbedShowLayout>
</Show>
);
};
+12 -17
View File
@@ -1,14 +1,15 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import { import {
Button, Button,
useMutation, useDelete,
useNotify, useNotify,
Confirm, Confirm,
useRecordContext,
useRefresh, useRefresh,
} from "react-admin"; } from "react-admin";
import ActionDelete from "@material-ui/icons/Delete"; import ActionDelete from "@mui/icons-material/Delete";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { alpha } from "@mui/material/styles";
import classnames from "classnames"; import classnames from "classnames";
const useStyles = makeStyles( const useStyles = makeStyles(
@@ -16,7 +17,7 @@ const useStyles = makeStyles(
deleteButton: { deleteButton: {
color: theme.palette.error.main, color: theme.palette.error.main,
"&:hover": { "&:hover": {
backgroundColor: fade(theme.palette.error.main, 0.12), backgroundColor: alpha(theme.palette.error.main, 0.12),
// Reset on mouse devices // Reset on mouse devices
"@media (hover: none)": { "@media (hover: none)": {
backgroundColor: "transparent", backgroundColor: "transparent",
@@ -28,13 +29,13 @@ const useStyles = makeStyles(
); );
export const DeviceRemoveButton = props => { export const DeviceRemoveButton = props => {
const { record } = props; const record = useRecordContext();
const classes = useStyles(props); const classes = useStyles(props);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
const [removeDevice, { loading }] = useMutation(); const [removeDevice, { isLoading }] = useDelete("devices");
if (!record) return null; if (!record) return null;
@@ -43,21 +44,15 @@ export const DeviceRemoveButton = props => {
const handleConfirm = () => { const handleConfirm = () => {
removeDevice( removeDevice(
{ { payload: { id: record.id, user_id: record.user_id } },
type: "delete",
resource: "devices",
payload: {
id: record.id,
user_id: record.user_id,
},
},
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.devices.action.erase.success"); notify("resources.devices.action.erase.success");
refresh(); refresh();
}, },
onFailure: () => onFailure: () => {
notify("resources.devices.action.erase.failure", "error"), notify("resources.devices.action.erase.failure", { type: "error" });
},
} }
); );
setOpen(false); setOpen(false);
@@ -74,7 +69,7 @@ export const DeviceRemoveButton = props => {
</Button> </Button>
<Confirm <Confirm
isOpen={open} isOpen={open}
loading={loading} loading={isLoading}
onConfirm={handleConfirm} onConfirm={handleConfirm}
onClose={handleDialogClose} onClose={handleDialogClose}
title="resources.devices.action.erase.title" title="resources.devices.action.erase.title"
+33 -20
View File
@@ -1,8 +1,7 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import classnames from "classnames"; import classnames from "classnames";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { alpha } from "@mui/material/styles";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { Tooltip } from "@material-ui/core";
import { import {
BooleanInput, BooleanInput,
Button, Button,
@@ -14,26 +13,30 @@ import {
useCreate, useCreate,
useDelete, useDelete,
useNotify, useNotify,
useRecordContext,
useRefresh, useRefresh,
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import BlockIcon from "@material-ui/icons/Block"; import BlockIcon from "@mui/icons-material/Block";
import ClearIcon from "@material-ui/icons/Clear"; import ClearIcon from "@mui/icons-material/Clear";
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep"; import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
import Dialog from "@material-ui/core/Dialog"; import {
import DialogContent from "@material-ui/core/DialogContent"; Dialog,
import DialogContentText from "@material-ui/core/DialogContentText"; DialogContent,
import DialogTitle from "@material-ui/core/DialogTitle"; DialogContentText,
import IconCancel from "@material-ui/icons/Cancel"; DialogTitle,
import LockIcon from "@material-ui/icons/Lock"; Tooltip,
import LockOpenIcon from "@material-ui/icons/LockOpen"; } from "@mui/material";
import IconCancel from "@mui/icons-material/Cancel";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
deleteButton: { deleteButton: {
color: theme.palette.error.main, color: theme.palette.error.main,
"&:hover": { "&:hover": {
backgroundColor: fade(theme.palette.error.main, 0.12), backgroundColor: alpha(theme.palette.error.main, 0.12),
// Reset on mouse devices // Reset on mouse devices
"@media (hover: none)": { "@media (hover: none)": {
backgroundColor: "transparent", backgroundColor: "transparent",
@@ -127,7 +130,9 @@ export const DeleteMediaButton = props => {
handleDialogClose(); handleDialogClose();
}, },
onFailure: () => onFailure: () =>
notify("resources.delete_media.action.send_failure", "error"), notify("resources.delete_media.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -152,7 +157,7 @@ export const DeleteMediaButton = props => {
}; };
export const ProtectMediaButton = props => { export const ProtectMediaButton = props => {
const { record } = props; const record = useRecordContext();
const translate = useTranslate(); const translate = useTranslate();
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
@@ -170,7 +175,9 @@ export const ProtectMediaButton = props => {
refresh(); refresh();
}, },
onFailure: () => onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"), notify("resources.protect_media.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -184,7 +191,9 @@ export const ProtectMediaButton = props => {
refresh(); refresh();
}, },
onFailure: () => onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"), notify("resources.protect_media.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -244,7 +253,7 @@ export const ProtectMediaButton = props => {
}; };
export const QuarantineMediaButton = props => { export const QuarantineMediaButton = props => {
const { record } = props; const record = useRecordContext();
const translate = useTranslate(); const translate = useTranslate();
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
@@ -262,7 +271,9 @@ export const QuarantineMediaButton = props => {
refresh(); refresh();
}, },
onFailure: () => onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"), notify("resources.quarantine_media.action.send_failure", {
type: "error",
}),
} }
); );
}; };
@@ -276,7 +287,9 @@ export const QuarantineMediaButton = props => {
refresh(); refresh();
}, },
onFailure: () => onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"), notify("resources.quarantine_media.action.send_failure", {
type: "error",
}),
} }
); );
}; };
+28 -28
View File
@@ -25,15 +25,15 @@ import {
import get from "lodash/get"; import get from "lodash/get";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { Tooltip, Typography, Chip } from "@material-ui/core"; import { Tooltip, Typography, Chip } from "@mui/material";
import FastForwardIcon from "@material-ui/icons/FastForward"; import FastForwardIcon from "@mui/icons-material/FastForward";
import HttpsIcon from "@material-ui/icons/Https"; import HttpsIcon from "@mui/icons-material/Https";
import NoEncryptionIcon from "@material-ui/icons/NoEncryption"; import NoEncryptionIcon from "@mui/icons-material/NoEncryption";
import PageviewIcon from "@material-ui/icons/Pageview"; import PageviewIcon from "@mui/icons-material/Pageview";
import UserIcon from "@material-ui/icons/Group"; import UserIcon from "@mui/icons-material/Group";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
import VisibilityIcon from "@material-ui/icons/Visibility"; import VisibilityIcon from "@mui/icons-material/Visibility";
import EventIcon from "@material-ui/icons/Event"; import EventIcon from "@mui/icons-material/Event";
import { import {
RoomDirectoryBulkDeleteButton, RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton, RoomDirectoryBulkSaveButton,
@@ -41,6 +41,15 @@ import {
RoomDirectorySaveButton, RoomDirectorySaveButton,
} from "./RoomDirectory"; } from "./RoomDirectory";
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
helper_forward_extremities: { helper_forward_extremities: {
fontFamily: "Roboto, Helvetica, Arial, sans-serif", fontFamily: "Roboto, Helvetica, Arial, sans-serif",
@@ -78,7 +87,8 @@ const EncryptionField = ({ source, record = {}, emptyText }) => {
); );
}; };
const RoomTitle = ({ record }) => { const RoomTitle = props => {
const record = useRecordContext();
const translate = useTranslate(); const translate = useTranslate();
var name = ""; var name = "";
if (record) { if (record) {
@@ -149,7 +159,11 @@ export const RoomShow = props => {
/> />
</Tab> </Tab>
<Tab label="synapseadmin.rooms.tabs.members" icon={<UserIcon />}> <Tab
label="synapseadmin.rooms.tabs.members"
icon={<UserIcon />}
path="members"
>
<ReferenceManyField <ReferenceManyField
reference="room_members" reference="room_members"
target="room_id" target="room_id"
@@ -247,14 +261,7 @@ export const RoomShow = props => {
<DateField <DateField
source="origin_server_ts" source="origin_server_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<TextField source="content" sortable={false} /> <TextField source="content" sortable={false} />
@@ -287,14 +294,7 @@ export const RoomShow = props => {
<DateField <DateField
source="received_ts" source="received_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<NumberField source="depth" sortable={false} /> <NumberField source="depth" sortable={false} />
@@ -355,7 +355,7 @@ const RoomFilter = ({ ...props }) => {
const RoomNameField = props => { const RoomNameField = props => {
const { source } = props; const { source } = props;
const record = useRecordContext(props); const record = useRecordContext();
return ( return (
<span>{record[source] || record["canonical_alias"] || record["id"]}</span> <span>{record[source] || record["canonical_alias"] || record["id"]}</span>
); );
+81 -89
View File
@@ -1,14 +1,14 @@
import React, { cloneElement, Fragment } from "react"; import React, { cloneElement, Fragment } from "react";
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@mui/material/Avatar";
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd"; import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
import ContactMailIcon from "@material-ui/icons/ContactMail"; import ContactMailIcon from "@mui/icons-material/ContactMail";
import DevicesIcon from "@material-ui/icons/Devices"; import DevicesIcon from "@mui/icons-material/Devices";
import GetAppIcon from "@material-ui/icons/GetApp"; import GetAppIcon from "@mui/icons-material/GetApp";
import NotificationsIcon from "@material-ui/icons/Notifications"; import NotificationsIcon from "@mui/icons-material/Notifications";
import PermMediaIcon from "@material-ui/icons/PermMedia"; import PermMediaIcon from "@mui/icons-material/PermMedia";
import PersonPinIcon from "@material-ui/icons/PersonPin"; import PersonPinIcon from "@mui/icons-material/PersonPin";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent"; import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
import { import {
ArrayInput, ArrayInput,
ArrayField, ArrayField,
@@ -39,6 +39,7 @@ import {
maxLength, maxLength,
regex, regex,
required, required,
useRecordContext,
useTranslate, useTranslate,
Pagination, Pagination,
CreateButton, CreateButton,
@@ -71,6 +72,25 @@ const useStyles = makeStyles({
}, },
}); });
const choices_medium = [
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
];
const choices_type = [
{ id: "bot", name: "bot" },
{ id: "support", name: "support" },
];
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const UserListActions = ({ const UserListActions = ({
currentSort, currentSort,
className, className,
@@ -180,14 +200,7 @@ export const UserList = props => {
source="creation_ts" source="creation_ts"
label="resources.users.fields.creation_ts_ms" label="resources.users.fields.creation_ts_ms"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/> />
</Datagrid> </Datagrid>
</List> </List>
@@ -247,20 +260,31 @@ export function generateRandomUser() {
}; };
} }
const UserEditToolbar = props => { const UserEditToolbar = props => (
const translate = useTranslate();
return (
<Toolbar {...props}> <Toolbar {...props}>
<SaveButton submitOnEnter={true} disabled={props.pristine} /> <SaveButton submitOnEnter={true} disabled={props.pristine} />
</Toolbar>
);
const UserEditActions = ({ data }) => {
const translate = useTranslate();
var userStatus = "";
if (data) {
userStatus = data.deactivated;
}
return (
<TopToolbar>
{!userStatus && <ServerNoticeButton record={data} />}
<DeleteButton <DeleteButton
record={data}
label="resources.users.action.erase" label="resources.users.action.erase"
confirmTitle={translate("resources.users.helper.erase", { confirmTitle={translate("resources.users.helper.erase", {
smart_count: 1, smart_count: 1,
})} })}
mutationMode="pessimistic" mutationMode="pessimistic"
/> />
<ServerNoticeButton /> </TopToolbar>
</Toolbar>
); );
}; };
@@ -274,22 +298,26 @@ export const UserCreate = props => (
autoComplete="new-password" autoComplete="new-password"
validate={maxLength(512)} validate={maxLength(512)}
/> />
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" /> <BooleanInput source="admin" />
<ArrayInput source="threepids"> <ArrayInput source="threepids">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<SelectInput <SelectInput
source="medium" source="medium"
choices={[ choices={choices_medium}
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
validate={required()} validate={required()}
/> />
<TextInput source="address" validate={validateAddress} /> <TextInput source="address" validate={validateAddress} />
</SimpleFormIterator> </SimpleFormIterator>
</ArrayInput> </ArrayInput>
<ArrayInput source="external_ids" label="synapseadmin.users.tabs.sso"> <ArrayInput source="external_ids" label="synapseadmin.users.tabs.sso">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<TextInput source="auth_provider" validate={required()} /> <TextInput source="auth_provider" validate={required()} />
<TextInput <TextInput
source="external_id" source="external_id"
@@ -302,7 +330,8 @@ export const UserCreate = props => (
</Create> </Create>
); );
const UserTitle = ({ record }) => { const UserTitle = props => {
const record = useRecordContext();
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<span> <span>
@@ -313,11 +342,12 @@ const UserTitle = ({ record }) => {
</span> </span>
); );
}; };
export const UserEdit = props => { export const UserEdit = props => {
const classes = useStyles(); const classes = useStyles();
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<Edit {...props} title={<UserTitle />}> <Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
<TabbedForm toolbar={<UserEditToolbar />}> <TabbedForm toolbar={<UserEditToolbar />}>
<FormTab <FormTab
label={translate("resources.users.name", { smart_count: 1 })} label={translate("resources.users.name", { smart_count: 1 })}
@@ -330,24 +360,24 @@ export const UserEdit = props => {
/> />
<TextInput source="id" disabled /> <TextInput source="id" disabled />
<TextInput source="displayname" /> <TextInput source="displayname" />
<PasswordInput source="password" autoComplete="new-password" /> <PasswordInput
source="password"
autoComplete="new-password"
helperText="resources.users.helper.password"
/>
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" /> <BooleanInput source="admin" />
<BooleanInput <BooleanInput
source="deactivated" source="deactivated"
helperText="resources.users.helper.deactivate" helperText="resources.users.helper.deactivate"
/> />
<DateField <DateField source="creation_ts_ms" showTime options={date_format} />
source="creation_ts_ms"
showTime
options={{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/>
<TextField source="consent_version" /> <TextField source="consent_version" />
</FormTab> </FormTab>
@@ -357,14 +387,8 @@ export const UserEdit = props => {
path="threepid" path="threepid"
> >
<ArrayInput source="threepids"> <ArrayInput source="threepids">
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<SelectInput <SelectInput source="medium" choices={choices_medium} />
source="medium"
choices={[
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
/>
<TextInput source="address" /> <TextInput source="address" />
</SimpleFormIterator> </SimpleFormIterator>
</ArrayInput> </ArrayInput>
@@ -376,7 +400,7 @@ export const UserEdit = props => {
path="sso" path="sso"
> >
<ArrayInput source="external_ids" label={false}> <ArrayInput source="external_ids" label={false}>
<SimpleFormIterator> <SimpleFormIterator disableReordering>
<TextInput source="auth_provider" validate={required()} /> <TextInput source="auth_provider" validate={required()} />
<TextInput <TextInput
source="external_id" source="external_id"
@@ -404,14 +428,7 @@ export const UserEdit = props => {
<DateField <DateField
source="last_seen_ts" source="last_seen_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<DeviceRemoveButton /> <DeviceRemoveButton />
@@ -439,14 +456,7 @@ export const UserEdit = props => {
<DateField <DateField
source="last_seen" source="last_seen"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false} sortable={false}
/> />
<TextField <TextField
@@ -473,29 +483,11 @@ export const UserEdit = props => {
sort={{ field: "created_ts", order: "DESC" }} sort={{ field: "created_ts", order: "DESC" }}
> >
<Datagrid style={{ width: "100%" }}> <Datagrid style={{ width: "100%" }}>
<DateField <DateField source="created_ts" showTime options={date_format} />
source="created_ts"
showTime
options={{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/>
<DateField <DateField
source="last_access_ts" source="last_access_ts"
showTime showTime
options={{ options={date_format}
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
/> />
<TextField source="media_id" /> <TextField source="media_id" />
<NumberField source="media_length" /> <NumberField source="media_length" />
+30 -2
View File
@@ -10,6 +10,7 @@ const de = {
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'", username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen", protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
url_error: "Keine gültige Matrix Server URL", url_error: "Keine gültige Matrix Server URL",
sso_sign_in: "Anmeldung mit SSO",
}, },
users: { users: {
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.", invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",
@@ -96,7 +97,6 @@ const de = {
}, },
resources: { resources: {
users: { users: {
backtolist: "Zurück zur Liste",
name: "Benutzer", name: "Benutzer",
email: "E-Mail", email: "E-Mail",
msisdn: "Telefon", msisdn: "Telefon",
@@ -121,8 +121,11 @@ const de = {
creation_ts_ms: "Zeitpunkt der Erstellung", creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen", consent_version: "Zugestimmte Geschäftsbedingungen",
auth_provider: "Provider", auth_provider: "Provider",
user_type: "Benutzertyp",
}, },
helper: { helper: {
password:
"Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
deactivate: deactivate:
"Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.", "Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
erase: "DSGVO konformes Löschen der Benutzerdaten", erase: "DSGVO konformes Löschen der Benutzerdaten",
@@ -352,6 +355,31 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.", send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
}, },
}, },
destinations: {
name: "Föderation",
fields: {
destination: "Ziel",
failure_ts: "Fehlerzeitpunkt",
retry_last_ts: "Letzter Wiederholungsversuch",
retry_interval: "Wiederholungsintervall",
last_successful_stream_ordering: "letzte erfogreicher Stream",
stream_ordering: "Stream",
},
action: { reconnect: "Neu verbinden" },
},
registration_tokens: {
name: "Registrierungstoken",
fields: {
token: "Token",
valid: "Gültige Token",
uses_allowed: "Anzahl",
pending: "Ausstehend",
completed: "Abgeschlossen",
expiry_time: "Ablaufzeit",
length: "Länge",
},
helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." },
},
}, },
ra: { ra: {
...germanMessages.ra, ...germanMessages.ra,
@@ -372,7 +400,7 @@ const de = {
}, },
}, },
notification: { notification: {
...germanMessages.ra.notifiaction, ...germanMessages.ra.notification,
logged_out: "Abgemeldet", logged_out: "Abgemeldet",
}, },
page: { page: {
+32 -3
View File
@@ -10,6 +10,7 @@ const en = {
username_error: "Please enter fully qualified user ID: '@user:domain'", username_error: "Please enter fully qualified user ID: '@user:domain'",
protocol_error: "URL has to start with 'http://' or 'https://'", protocol_error: "URL has to start with 'http://' or 'https://'",
url_error: "Not a valid Matrix server URL", url_error: "Not a valid Matrix server URL",
sso_sign_in: "Sign in with SSO",
}, },
users: { users: {
invalid_user_id: "Localpart of a Matrix user-id without homeserver.", invalid_user_id: "Localpart of a Matrix user-id without homeserver.",
@@ -95,7 +96,6 @@ const en = {
}, },
resources: { resources: {
users: { users: {
backtolist: "Back to list",
name: "User |||| Users", name: "User |||| Users",
email: "Email", email: "Email",
msisdn: "Phone", msisdn: "Phone",
@@ -120,8 +120,10 @@ const en = {
creation_ts_ms: "Creation timestamp", creation_ts_ms: "Creation timestamp",
consent_version: "Consent version", consent_version: "Consent version",
auth_provider: "Provider", auth_provider: "Provider",
user_type: "User type",
}, },
helper: { helper: {
password: "Changing password will log user out of all sessions.",
deactivate: "You must provide a password to re-activate an account.", deactivate: "You must provide a password to re-activate an account.",
erase: "Mark the user as GDPR-erased", erase: "Mark the user as GDPR-erased",
}, },
@@ -174,12 +176,14 @@ const en = {
}, },
unencrypted: "Unencrypted", unencrypted: "Unencrypted",
}, },
action: {
erase: { erase: {
title: "Delete room", title: "Delete room",
content: content:
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!", "Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!",
}, },
}, },
},
reports: { reports: {
name: "Reported event |||| Reported events", name: "Reported event |||| Reported events",
fields: { fields: {
@@ -193,7 +197,7 @@ const en = {
event_json: { event_json: {
origin: "origin server", origin: "origin server",
origin_server_ts: "time of send", origin_server_ts: "time of send",
type: "event typ", type: "event type",
content: { content: {
msgtype: "content type", msgtype: "content type",
body: "content", body: "content",
@@ -341,13 +345,38 @@ const en = {
title: title:
"Delete room from directory |||| Delete %{smart_count} rooms from directory", "Delete room from directory |||| Delete %{smart_count} rooms from directory",
content: content:
"Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory", "Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory?",
erase: "Delete from room directory", erase: "Delete from room directory",
create: "Publish in room directory", create: "Publish in room directory",
send_success: "Room successfully published.", send_success: "Room successfully published.",
send_failure: "An error has occurred.", send_failure: "An error has occurred.",
}, },
}, },
destinations: {
name: "Federation",
fields: {
destination: "Destination",
failure_ts: "Failure timestamp",
retry_last_ts: "Last retry timestamp",
retry_interval: "Retry interval",
last_successful_stream_ordering: "Last successful stream",
stream_ordering: "Stream",
},
action: { reconnect: "Reconnect" },
},
},
registration_tokens: {
name: "Registration tokens",
fields: {
token: "Token",
valid: "Valid token",
uses_allowed: "Uses allowed",
pending: "Pending",
completed: "Completed",
expiry_time: "Expiry time",
length: "Length",
},
helper: { length: "Length of the token if no token is given." },
}, },
}; };
export default en; export default en;
+377
View File
@@ -0,0 +1,377 @@
import frenchMessages from "ra-language-french";
const fr = {
...frenchMessages,
synapseadmin: {
auth: {
base_url: "URL du serveur daccueil",
welcome: "Bienvenue sur Synapse-admin",
server_version: "Version du serveur Synapse",
username_error:
"Veuillez entrer un nom d'utilisateur complet : « @utilisateur:domaine »",
protocol_error: "L'URL doit commencer par « http:// » ou « https:// »",
url_error: "L'URL du serveur Matrix n'est pas valide",
sso_sign_in: "Se connecter avec lauthentification unique",
},
users: {
invalid_user_id:
"Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur daccueil.",
tabs: { sso: "Authentification unique" },
},
rooms: {
tabs: {
basic: "Informations de base",
members: "Membres",
detail: "Détails",
permission: "Permissions",
},
},
reports: { tabs: { basic: "Informations de base", detail: "Détails" } },
},
import_users: {
error: {
at_entry: "Pour l'entrée %{entry} : %{message}",
error: "Erreur",
required_field: "Le champ requis « %{field} » est manquant",
invalid_value:
"Valeur non valide à la ligne %{row}. Le champ « %{field} » ne peut être que « true » ou « false »",
unreasonably_big:
"Refus de charger un fichier trop volumineux de %{size} mégaoctets",
already_in_progress: "Un import est déjà en cours",
id_exits: "L'identifiant %{id} déjà présent",
},
title: "Importer des utilisateurs à partir d'un fichier CSV",
goToPdf: "Voir le PDF",
cards: {
importstats: {
header: "Importer des utilisateurs",
users_total:
"%{smart_count} utilisateur dans le fichier CSV |||| %{smart_count} utilisateurs dans le fichier CSV",
guest_count: "%{smart_count} visiteur |||| %{smart_count} visiteurs",
admin_count:
"%{smart_count} administrateur |||| %{smart_count} administrateurs",
},
conflicts: {
header: "Stratégie de résolution des conflits",
mode: {
stop: "S'arrêter en cas de conflit",
skip: "Afficher l'erreur et ignorer le conflit",
},
},
ids: {
header: "Identifiants",
all_ids_present: "Identifiants présents pour chaque entrée",
count_ids_present:
"%{smart_count} entrée avec identifiant |||| %{smart_count} entrées avec identifiant",
mode: {
ignore:
"Ignorer les identifiants dans le ficher CSV et en créer de nouveaux",
update: "Mettre à jour les enregistrements existants",
},
},
passwords: {
header: "Mots de passe",
all_passwords_present: "Mots de passe présents pour chaque entrée",
count_passwords_present:
"%{smart_count} entrée avec mot de passe |||| %{smart_count} entrées avec mot de passe",
use_passwords: "Utiliser les mots de passe provenant du fichier CSV",
},
upload: {
header: "Fichier CSV en entrée",
explanation:
"Vous pouvez télécharger ici un fichier contenant des valeurs séparées par des virgules qui sera traité pour créer ou mettre à jour des utilisateurs. Le fichier doit inclure les champs « id » et « displayname ». Vous pouvez télécharger et adapter un fichier d'exemple ici : ",
},
startImport: {
simulate_only: "Simuler",
run_import: "Importer",
},
results: {
header: "Résultats de l'import",
total:
"%{smart_count} entrée au total |||| %{smart_count} entrées au total",
successful: "%{smart_count} entrées importées avec succès",
skipped: "%{smart_count} entrées ignorées",
download_skipped: "Télécharger les entrées ignorées",
with_error:
"%{smart_count} entrée avec des erreurs ||| %{smart_count} entrées avec des erreurs",
simulated_only: "L'import était simulé",
},
},
},
resources: {
users: {
name: "Utilisateur |||| Utilisateurs",
email: "Adresse électronique",
msisdn: "Numéro de téléphone",
threepid: "Adresse électronique / Numéro de téléphone",
fields: {
avatar: "Avatar",
id: "Identifiant",
name: "Nom",
is_guest: "Visiteur",
admin: "Administrateur du serveur",
deactivated: "Désactivé",
guests: "Afficher les visiteurs",
show_deactivated: "Afficher les utilisateurs désactivés",
user_id: "Rechercher un utilisateur",
displayname: "Nom d'affichage",
password: "Mot de passe",
avatar_url: "URL de l'avatar",
avatar_src: "Avatar",
medium: "Type",
threepids: "Identifiants tiers",
address: "Adresse",
creation_ts_ms: "Date de création",
consent_version: "Version du consentement",
auth_provider: "Fournisseur d'identité",
},
helper: {
deactivate:
"Vous devrez fournir un mot de passe pour réactiver le compte.",
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
},
action: {
erase: "Effacer les données de l'utilisateur",
},
},
rooms: {
name: "Salon |||| Salons",
fields: {
room_id: "Identifiant du salon",
name: "Nom",
canonical_alias: "Alias",
joined_members: "Membres",
joined_local_members: "Membres locaux",
joined_local_devices: "Appareils locaux",
state_events: "Événements d'État / Complexité",
version: "Version",
is_encrypted: "Chiffré",
encryption: "Chiffrement",
federatable: "Fédérable",
public: "Visible dans le répertoire des salons",
creator: "Créateur",
join_rules: "Règles d'adhésion",
guest_access: "Accès des visiteurs",
history_visibility: "Visibilité de l'historique",
topic: "Sujet",
avatar: "Avatar",
},
helper: {
forward_extremities:
"Les extrémités avant sont les événements feuilles à la fin d'un graphe orienté acyclique (DAG) dans un salon, c'est-à-dire les événements qui n'ont pas de descendants. Plus il y en a dans un salon, plus la résolution d'état que Synapse doit effectuer est importante (indice : c'est une opération coûteuse). Bien que Synapse dispose d'un algorithme pour éviter qu'un trop grand nombre de ces événements n'existent en même temps dans un salon, des bogues peuvent parfois les faire réapparaître. Si un salon présente plus de 10 extrémités avant, cela vaut la peine d'y prêter attention et éventuellement de les supprimer en utilisant les requêtes SQL mentionnées dans la discussion traitant du problème https://github.com/matrix-org/synapse/issues/1760.",
},
enums: {
join_rules: {
public: "Public",
knock: "Sur demande",
invite: "Sur invitation",
private: "Privé",
},
guest_access: {
can_join: "Les visiteurs peuvent rejoindre le salon",
forbidden: "Les visiteurs ne peuvent pas rejoindre le salon",
},
history_visibility: {
invited: "Depuis l'invitation",
joined: "Depuis l'adhésion",
shared: "Depuis le partage",
world_readable: "Tout le monde",
},
unencrypted: "Non chiffré",
},
action: {
erase: {
title: "Supprimer le salon",
content:
"Voulez-vous vraiment supprimer le salon ? Cette opération ne peut être annulée. Tous les messages et médias partagés du salon seront supprimés du serveur !",
},
},
},
reports: {
name: "Événement signalé |||| Événements signalés",
fields: {
id: "Identifiant",
received_ts: "Date du rapport",
user_id: "Rapporteur",
name: "Nom du salon",
score: "Score",
reason: "Motif",
event_id: "Identifiant de l'événement",
event_json: {
origin: "Serveur d'origine",
origin_server_ts: "Date d'envoi",
type: "Type d'événement",
content: {
msgtype: "Type de contenu",
body: "Contenu",
format: "Format",
formatted_body: "Contenu mis en forme",
algorithm: "Algorithme",
},
},
},
},
connections: {
name: "Connexions",
fields: {
last_seen: "Date",
ip: "Adresse IP",
user_agent: "Agent utilisateur",
},
},
devices: {
name: "Appareil |||| Appareils",
fields: {
device_id: "Identifiant de l'appareil",
display_name: "Nom de l'appareil",
last_seen_ts: "Date",
last_seen_ip: "Adresse IP",
},
action: {
erase: {
title: "Suppression de %{id}",
content: "Voulez-vous vraiment supprimer l'appareil « %{name} » ?",
success: "Appareil supprimé avec succès",
failure: "Une erreur s'est produite",
},
},
},
users_media: {
name: "Media",
fields: {
media_id: "Identifiant du média",
media_length: "Taille du fichier (en octets)",
media_type: "Type",
upload_name: "Nom du fichier",
quarantined_by: "Mis en quarantaine par",
safe_from_quarantine: "Protection contre la mise en quarantaine",
created_ts: "Date de création",
last_access_ts: "Dernier accès",
},
},
delete_media: {
name: "Media",
fields: {
before_ts: "Dernier accès avant",
size_gt: "Plus grand que (en octets)",
keep_profiles: "Conserver les images de profil",
},
action: {
send: "Supprimer le média",
send_success: "Requête envoyée avec succès",
send_failure: "Une erreur s'est produite",
},
helper: {
send: "Cette API supprime les médias locaux du disque de votre propre serveur. Cela inclut toutes les vignettes locales et les copies des médias téléchargés. Cette API n'affectera pas les médias qui ont été téléversés dans des dépôts de médias externes.",
},
},
protect_media: {
action: {
create: "Protéger",
delete: "Révoquer la protection",
none: "En quarantaine",
send_success: "Le statut de protection a été modifié avec succès",
send_failure: "Une erreur s'est produite",
},
},
quarantine_media: {
action: {
name: "Quarantaine",
create: "Mettre en quarantaine",
delete: "Révoquer la mise en quarantaine",
none: "Protégé contre la mise en quarantaine",
send_success: "Le statut de la quarantaine a été modifié avec succès",
send_failure: "Une erreur s'est produite",
},
},
pushers: {
name: "Émetteur de notifications |||| Émetteurs de notifications",
fields: {
app: "Application",
app_display_name: "Nom d'affichage de l'application",
app_id: "Identifiant de l'application",
device_display_name: "Nom d'affichage de l'appareil",
kind: "Type",
lang: "Langue",
profile_tag: "Profil",
pushkey: "Identifiant de l'émetteur",
data: { url: "URL" },
},
},
servernotices: {
name: "Annonces du serveur",
send: "Envoyer des « Annonces du serveur »",
fields: {
body: "Message",
},
action: {
send: "Envoyer une annonce",
send_success: "Annonce envoyée avec succès",
send_failure: "Une erreur s'est produite",
},
helper: {
send: "Envoie une annonce au nom du serveur aux utilisateurs sélectionnés. La fonction « Annonces du serveur » doit être activée sur le serveur.",
},
},
user_media_statistics: {
name: "Médias des utilisateurs",
fields: {
media_count: "Nombre de médias",
media_length: "Taille des médias",
},
},
forward_extremities: {
name: "Extrémités avant",
fields: {
id: "Identifiant de l'événement",
received_ts: "Date de réception",
depth: "Profondeur",
state_group: "Groupe d'état",
},
},
room_state: {
name: "Événements d'état",
fields: {
type: "Type",
content: "Contenu",
origin_server_ts: "Date d'envoi",
sender: "Expéditeur",
},
},
room_directory: {
name: "Répertoire des salons",
fields: {
world_readable:
"Tout utilisateur peut avoir un aperçu du salon, sans en devenir membre",
guest_can_join: "Les visiteurs peuvent rejoindre le salon",
},
action: {
title:
"Supprimer un salon du répertoire |||| Supprimer %{smart_count} salons du répertoire",
content:
"Voulez-vous vraiment supprimer ce salon du répertoire ? |||| Voulez-vous vraiment supprimer ces %{smart_count} salons du répertoire ?",
erase: "Supprimer du répertoire des salons",
create: "Publier dans le répertoire des salons",
send_success: "Salon publié avec succès",
send_failure: "Une erreur s'est produite",
},
},
},
registration_tokens: {
name: "Jetons d'inscription",
fields: {
token: "Jeton",
valid: "Jeton valide",
uses_allowed: "Nombre d'inscription autorisées",
pending: "Nombre d'inscription en cours",
completed: "Nombre d'inscription accomplie",
expiry_time: "Date d'expiration",
length: "Longueur",
},
helper: {
length:
"Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié",
},
},
};
export default fr;
+2 -1
View File
@@ -10,10 +10,12 @@ const zh = {
username_error: "请输入完整有效的用户 ID: '@user:domain'", username_error: "请输入完整有效的用户 ID: '@user:domain'",
protocol_error: "URL 需要以'http://'或'https://'作为起始", protocol_error: "URL 需要以'http://'或'https://'作为起始",
url_error: "不是一个有效的 Matrix 服务器地址", url_error: "不是一个有效的 Matrix 服务器地址",
sso_sign_in: "使用 SSO 登录",
}, },
users: { users: {
invalid_user_id: invalid_user_id:
"必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver", "必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",
tabs: { sso: "SSO" },
}, },
rooms: { rooms: {
tabs: { tabs: {
@@ -98,7 +100,6 @@ const zh = {
}, },
resources: { resources: {
users: { users: {
backtolist: "回到列表",
name: "用户", name: "用户",
email: "邮箱", email: "邮箱",
msisdn: "电话", msisdn: "电话",
+16 -5
View File
@@ -2,20 +2,31 @@ import { fetchUtils } from "react-admin";
const authProvider = { const authProvider = {
// called when the user attempts to log in // called when the user attempts to log in
login: ({ base_url, username, password }) => { login: ({ base_url, username, password, loginToken }) => {
// force homeserver for protection in case the form is manipulated // force homeserver for protection in case the form is manipulated
base_url = process.env.REACT_APP_SERVER || base_url; base_url = process.env.REACT_APP_SERVER || base_url;
console.log("login "); console.log("login ");
const options = { const options = {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify(
Object.assign(
{
device_id: localStorage.getItem("device_id"),
initial_device_display_name: "Synapse Admin",
},
loginToken
? {
type: "m.login.token",
token: loginToken,
}
: {
type: "m.login.password", type: "m.login.password",
user: username, user: username,
password: password, password: password,
device_id: localStorage.getItem("device_id"), }
initial_device_display_name: "Synapse Admin", )
}), ),
}; };
// use the base_url from login instead of the well_known entry from the // use the base_url from login instead of the well_known entry from the
+89 -18
View File
@@ -41,14 +41,16 @@ const resourceMap = {
data: "users", data: "users",
total: json => json.total, total: json => json.total,
create: data => ({ create: data => ({
endpoint: `/_synapse/admin/v2/users/@${data.id}:${localStorage.getItem( endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(
"home_server" data.id
)}`, )}:${localStorage.getItem("home_server")}`,
body: data, body: data,
method: "PUT", method: "PUT",
}), }),
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/deactivate/${params.id}`, endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(
params.id
)}`,
body: { erase: true }, body: { erase: true },
method: "POST", method: "POST",
}), }),
@@ -69,7 +71,7 @@ const resourceMap = {
return json.total_rooms; return json.total_rooms;
}, },
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/rooms/${params.id}`, endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
body: { block: false }, body: { block: false },
}), }),
}, },
@@ -92,10 +94,12 @@ const resourceMap = {
return json.total; return json.total;
}, },
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v2/users/${id}/devices`, endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(id)}/devices`,
}), }),
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v2/users/${params.user_id}/devices/${params.id}`, endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
params.user_id
)}/devices/${params.id}`,
}), }),
}, },
connections: { connections: {
@@ -137,7 +141,7 @@ const resourceMap = {
id: p.pushkey, id: p.pushkey,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/pushers`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/pushers`,
}), }),
data: "pushers", data: "pushers",
total: json => { total: json => {
@@ -149,7 +153,9 @@ const resourceMap = {
id: jr, id: jr,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/joined_rooms`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(
id
)}/joined_rooms`,
}), }),
data: "joined_rooms", data: "joined_rooms",
total: json => { total: json => {
@@ -162,7 +168,7 @@ const resourceMap = {
id: um.media_id, id: um.media_id,
}), }),
reference: id => ({ reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/media`, endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
}), }),
data: "media", data: "media",
total: json => { total: json => {
@@ -275,11 +281,59 @@ const resourceMap = {
method: "PUT", method: "PUT",
}), }),
}, },
destinations: {
path: "/_synapse/admin/v1/federation/destinations",
map: dst => ({
...dst,
id: dst.destination,
}),
data: "destinations",
total: json => {
return json.total;
},
delete: params => ({
endpoint: `/_synapse/admin/v1/federation/destinations/${params.id}/reset_connection`,
method: "POST",
}),
},
destination_rooms: {
map: dstroom => ({
...dstroom,
id: dstroom.room_id,
}),
reference: id => ({
endpoint: `/_synapse/admin/v1/federation/destinations/${id}/rooms`,
}),
data: "rooms",
total: json => {
return json.total;
},
},
registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({
...rt,
id: rt.token,
}),
data: "registration_tokens",
total: json => {
return json.registration_tokens.length;
},
create: params => ({
endpoint: "/_synapse/admin/v1/registration_tokens/new",
body: params,
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/registration_tokens/${params.id}`,
}),
},
}; };
function filterNullValues(key, value) { function filterNullValues(key, value) {
// Filtering out null properties // Filtering out null properties
if (value === null) { // to reset user_type from user, it must be null
if (value === null && key !== "user_type") {
return undefined; return undefined;
} }
return value; return value;
@@ -296,7 +350,15 @@ function getSearchOrder(order) {
const dataProvider = { const dataProvider = {
getList: (resource, params) => { getList: (resource, params) => {
console.log("getList " + resource); console.log("getList " + resource);
const { user_id, name, guests, deactivated, search_term } = params.filter; const {
user_id,
name,
guests,
deactivated,
search_term,
destination,
valid,
} = params.filter;
const { page, perPage } = params.pagination; const { page, perPage } = params.pagination;
const { field, order } = params.sort; const { field, order } = params.sort;
const from = (page - 1) * perPage; const from = (page - 1) * perPage;
@@ -306,8 +368,10 @@ const dataProvider = {
user_id: user_id, user_id: user_id,
search_term: search_term, search_term: search_term,
name: name, name: name,
destination: destination,
guests: guests, guests: guests,
deactivated: deactivated, deactivated: deactivated,
valid: valid,
order_by: field, order_by: field,
dir: getSearchOrder(order), dir: getSearchOrder(order),
}; };
@@ -333,9 +397,11 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`).then(({ json }) => ({ return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`).then(
({ json }) => ({
data: res.map(json), data: res.map(json),
})); })
);
}, },
getMany: (resource, params) => { getMany: (resource, params) => {
@@ -347,7 +413,9 @@ const dataProvider = {
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return Promise.all( return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`)) params.ids.map(id =>
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
)
).then(responses => ({ ).then(responses => ({
data: responses.map(({ json }) => res.map(json)), data: responses.map(({ json }) => res.map(json)),
total: responses.length, total: responses.length,
@@ -388,7 +456,7 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.data.id}`, { return jsonClient(`${endpoint_url}/${encodeURIComponent(params.data.id)}`, {
method: "PUT", method: "PUT",
body: JSON.stringify(params.data, filterNullValues), body: JSON.stringify(params.data, filterNullValues),
}).then(({ json }) => ({ }).then(({ json }) => ({
@@ -405,10 +473,13 @@ const dataProvider = {
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return Promise.all( return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`), { params.ids.map(
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
{
method: "PUT", method: "PUT",
body: JSON.stringify(params.data, filterNullValues), body: JSON.stringify(params.data, filterNullValues),
}) }
)
).then(responses => ({ ).then(responses => ({
data: responses.map(({ json }) => json), data: responses.map(({ json }) => json),
})); }));
+5488 -7205
View File
File diff suppressed because it is too large Load Diff