Compare commits

73 Commits

Author SHA1 Message Date
Aine
fe4ba22a03 update README 2024-10-30 18:49:32 +02:00
Aine
eb35d9e122 remove CI hack to include version into manifest.json (now generated on each build automatically) 2024-10-30 18:00:05 +02:00
Aine
fa79fecc9d Generate extended manifest.json on build 2024-10-30 17:58:33 +02:00
dependabot[bot]
61366b3792 Bump ra-language-english from 5.3.0 to 5.3.1 (#102)
Bumps [ra-language-english](https://github.com/marmelab/react-admin) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: ra-language-english
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:55:00 +02:00
dependabot[bot]
b55033d983 Bump react-admin from 5.3.0 to 5.3.1 (#103)
Bumps [react-admin](https://github.com/marmelab/react-admin) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: react-admin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:54:46 +02:00
dependabot[bot]
b1f42988c8 Bump ra-language-french from 5.3.0 to 5.3.1 (#104)
Bumps [ra-language-french](https://github.com/marmelab/react-admin) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: ra-language-french
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:54:27 +02:00
dependabot[bot]
ef05b366c3 Bump eslint-plugin-jsx-a11y from 6.10.1 to 6.10.2 (#105)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.10.1 to 6.10.2.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.10.1...v6.10.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:53:30 +02:00
dependabot[bot]
b3d52e7d23 Bump @types/node from 22.7.9 to 22.8.1 (#106)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.9 to 22.8.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:53:18 +02:00
dependabot[bot]
d1c4250b46 Bump @tanstack/react-query from 5.59.15 to 5.59.16 (#107)
Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.59.15 to 5.59.16.
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.59.16/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:53:04 +02:00
dependabot[bot]
8d44077b24 Bump ra-core from 5.3.0 to 5.3.1 (#109)
Bumps [ra-core](https://github.com/marmelab/react-admin) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: ra-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 10:52:37 +02:00
Aine
44e8b82412 Sanitize CSV on import (#101)
* Sanitize CSV on import

* update readme
2024-10-25 11:52:26 +03:00
Aine
791ae2c869 update naming 2024-10-24 21:49:00 +03:00
Aine
ee3753466a update manifest 2024-10-24 17:27:10 +03:00
Aine
b7dc703157 add community room to the footer 2024-10-24 14:17:02 +03:00
Aine
2eca0dcc33 better visual 2024-10-24 14:11:59 +03:00
Aine
d2219c1667 try adding community room to the readme 2024-10-24 14:11:23 +03:00
Aine
132ea6f97f add images 2024-10-24 13:44:12 +03:00
Aine
28ef08de03 Fix room state events display (#100)
* Fix room state events table display

* update readme
2024-10-23 13:48:21 +03:00
Aine
865e53388e fix yarn lint 2024-10-23 12:31:52 +03:00
dependabot[bot]
3a105bb8c7 Bump eslint from 8.57.1 to 9.13.0 (#99)
Bumps [eslint](https://github.com/eslint/eslint) from 8.57.1 to 9.13.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/v8.57.1...v9.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:14:33 +03:00
dependabot[bot]
edcda7a202 Bump @mui/icons-material from 6.1.1 to 6.1.5 (#98)
Bumps [@mui/icons-material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material) from 6.1.1 to 6.1.5.
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v6.1.5/packages/mui-icons-material)

---
updated-dependencies:
- dependency-name: "@mui/icons-material"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:13:59 +03:00
dependabot[bot]
edd69273e2 Bump typescript-eslint from 8.10.0 to 8.11.0 (#88)
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.10.0 to 8.11.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.11.0/packages/typescript-eslint)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:10:43 +03:00
dependabot[bot]
444bfacbd9 Bump eslint-plugin-unused-imports from 3.2.0 to 4.1.4 (#93)
Bumps [eslint-plugin-unused-imports](https://github.com/sweepline/eslint-plugin-unused-imports) from 3.2.0 to 4.1.4.
- [Commits](https://github.com/sweepline/eslint-plugin-unused-imports/commits/v4.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:07:18 +03:00
dependabot[bot]
970e0a550f Bump @types/node from 22.7.7 to 22.7.9 (#91)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.7 to 22.7.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:06:41 +03:00
dependabot[bot]
b3ef68d66e Bump @types/jest from 29.5.13 to 29.5.14 (#95)
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.13 to 29.5.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:06:26 +03:00
dependabot[bot]
31382a42ee Bump @typescript-eslint/eslint-plugin from 8.10.0 to 8.11.0 (#92)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.10.0 to 8.11.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.11.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:06:11 +03:00
dependabot[bot]
1a7748d1ef Bump @vitejs/plugin-react from 4.3.2 to 4.3.3 (#87)
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.3/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:05:51 +03:00
dependabot[bot]
039b28cc5c Bump vite from 5.4.6 to 5.4.10 (#89)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.6 to 5.4.10.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.10/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:03:42 +03:00
dependabot[bot]
57eae3edb3 Bump @mui/material from 6.1.1 to 6.1.5 (#94)
Bumps [@mui/material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-material) from 6.1.1 to 6.1.5.
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/v6.1.5/packages/mui-material)

---
updated-dependencies:
- dependency-name: "@mui/material"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:03:20 +03:00
dependabot[bot]
dadc9416c0 Bump eslint-plugin-jsx-a11y from 6.10.0 to 6.10.1 (#97)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.10.0 to 6.10.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.10.0...v6.10.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:03:03 +03:00
dependabot[bot]
eab2342114 Bump @types/lodash from 4.17.7 to 4.17.12 (#96)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.17.7 to 4.17.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:02:40 +03:00
dependabot[bot]
9cf2f83936 Bump ra-language-french from 5.2.0 to 5.3.0 (#90)
Bumps [ra-language-french](https://github.com/marmelab/react-admin) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: ra-language-french
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:02:09 +03:00
dependabot[bot]
d823856873 Bump @typescript-eslint/parser from 8.10.0 to 8.11.0 (#86)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.10.0 to 8.11.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.11.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:01:17 +03:00
dependabot[bot]
9b96c7cec8 Bump @eslint/js from 9.10.0 to 9.13.0 (#85)
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.10.0 to 9.13.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/commits/v9.13.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:01:01 +03:00
dependabot[bot]
f211aba873 Bump @types/react from 18.3.8 to 18.3.12 (#84)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.3.8 to 18.3.12.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:00:50 +03:00
dependabot[bot]
c0fc2d8937 Bump @tanstack/react-query from 5.56.2 to 5.59.15 (#83)
Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.56.2 to 5.59.15.
- [Release notes](https://github.com/TanStack/query/releases)
- [Commits](https://github.com/TanStack/query/commits/v5.59.15/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 12:00:37 +03:00
Aine
a88b397748 increase dependabot PR limits 2024-10-23 11:58:50 +03:00
Aine
abc922c956 Enable visual customization (#81)
* Enable visual customization

* update readme
2024-10-23 00:03:06 +03:00
Aine
4f2cd38344 Add user profile to the top menu (#80)
* Add user profile to the top menu

* update readme
2024-10-22 12:18:55 +03:00
Aine
ca71038874 Custom Menu Items (#79)
* Custom Menu Items

* update readme

* fix indentation
2024-10-21 23:33:51 +03:00
Borislav Pantaleev
be867b6b0d Fix types and missing translations (#78) 2024-10-21 19:37:29 +03:00
Aine
f2f540b429 fix footer content overlapping and scrollbar 2024-10-21 13:27:32 +03:00
Aine
7feec4ba07 update eslint typescript 2024-10-21 12:01:12 +03:00
Aine
1d5fef1e53 add missing package 2024-10-21 11:43:26 +03:00
dependabot[bot]
9c40efde17 Bump typescript-eslint from 7.18.0 to 8.10.0 (#76)
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 7.18.0 to 8.10.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.10.0/packages/typescript-eslint)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 11:34:26 +03:00
dependabot[bot]
53dff66978 Bump react-admin from 5.2.0 to 5.3.0 (#77)
Bumps [react-admin](https://github.com/marmelab/react-admin) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: react-admin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 11:31:09 +03:00
dependabot[bot]
3a595247e8 Bump vite-plugin-version-mark from 0.1.1 to 0.1.2 (#75)
Bumps [vite-plugin-version-mark](https://github.com/ZhongxuYang/vite-plugin-version-mark) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/ZhongxuYang/vite-plugin-version-mark/releases)
- [Changelog](https://github.com/ZhongxuYang/vite-plugin-version-mark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ZhongxuYang/vite-plugin-version-mark/compare/v0.1.1...v0.1.2)

---
updated-dependencies:
- dependency-name: vite-plugin-version-mark
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 11:29:57 +03:00
dependabot[bot]
33f5f60e31 Bump eslint-plugin-import from 2.30.0 to 2.31.0 (#74)
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.30.0 to 2.31.0.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.30.0...v2.31.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 11:29:35 +03:00
dependabot[bot]
9dd2ea57c9 Bump @types/papaparse from 5.3.14 to 5.3.15 (#73)
Bumps [@types/papaparse](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/papaparse) from 5.3.14 to 5.3.15.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/papaparse)

---
updated-dependencies:
- dependency-name: "@types/papaparse"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 11:29:16 +03:00
dependabot[bot]
fae7a696de Bump ra-i18n-polyglot from 5.2.0 to 5.3.0 (#68)
Bumps [ra-i18n-polyglot](https://github.com/marmelab/react-admin) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: ra-i18n-polyglot
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:23:56 +03:00
dependabot[bot]
49e8b2d0f5 Bump vite-plugin-version-mark from 0.1.0 to 0.1.1 (#72)
Bumps [vite-plugin-version-mark](https://github.com/ZhongxuYang/vite-plugin-version-mark) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/ZhongxuYang/vite-plugin-version-mark/releases)
- [Changelog](https://github.com/ZhongxuYang/vite-plugin-version-mark/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ZhongxuYang/vite-plugin-version-mark/compare/v0.1.0...v0.1.1)

---
updated-dependencies:
- dependency-name: vite-plugin-version-mark
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:22:15 +03:00
dependabot[bot]
281d908d3f Bump ra-language-english from 5.2.0 to 5.3.0 (#71)
Bumps [ra-language-english](https://github.com/marmelab/react-admin) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: ra-language-english
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:21:57 +03:00
dependabot[bot]
bacc42fe9c Bump react-router-dom from 6.26.2 to 6.27.0 (#70)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.26.2 to 6.27.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.27.0/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.27.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:21:44 +03:00
dependabot[bot]
1c26a28ca9 Bump react-hook-form from 7.53.0 to 7.53.1 (#69)
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.53.0 to 7.53.1.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.53.0...v7.53.1)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:21:33 +03:00
dependabot[bot]
d3a04cd132 Bump @vitejs/plugin-react from 4.3.1 to 4.3.2 (#67)
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.2/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:20:58 +03:00
dependabot[bot]
e6060a23ac Bump @types/node from 20.16.5 to 22.7.7 (#66)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.16.5 to 22.7.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:20:43 +03:00
dependabot[bot]
4b7fbf483a Bump typescript from 5.6.2 to 5.6.3 (#65)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.6.2 to 5.6.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.6.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:20:31 +03:00
dependabot[bot]
bc3c30da92 Bump @testing-library/jest-dom from 6.5.0 to 6.6.2 (#64)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.5.0 to 6.6.2.
- [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/v6.5.0...v6.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:20:02 +03:00
dependabot[bot]
1896f770d1 Bump ra-core from 5.2.0 to 5.3.0 (#63)
Bumps [ra-core](https://github.com/marmelab/react-admin) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/marmelab/react-admin/releases)
- [Changelog](https://github.com/marmelab/react-admin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/marmelab/react-admin/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: ra-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:19:34 +03:00
dependabot[bot]
99d0b9ad72 Bump docker/setup-buildx-action from 1 to 3 (#62)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v3)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-19 16:19:06 +03:00
Aine
944afb9056 bring back dependabot config 2024-10-19 16:15:23 +03:00
Aine
23f5a24803 update README.md 2024-10-19 15:57:12 +03:00
Borislav Pantaleev
60ae00ac14 Add seprate login button for access token (#61) 2024-10-19 12:03:46 +00:00
Borislav Pantaleev
26862fa708 Add etke.cc to footer and package.json (#60)
* Add etke.cc to footer and package.json

* more links
2024-10-19 11:24:45 +03:00
Aine
853d14c1ce fix 'Sign in' button disabled on SSO-only servers when attempting access token login 2024-10-18 10:21:48 +03:00
jamazi
11a5cac709 Refactor required fields check on Bulk CSV upload (#59)
Related to https://github.com/etkecc/synapse-admin/pull/32
2024-10-17 22:56:19 +03:00
Borislav Pantaleev
0d021021df Add option for access token login (#58)
* Fix SSO login flow, redirect is done after auth

* Add accessToken login

* Add confirmation for session destroy on accessToken logout

* add translations, fix tests, minor renaming

* update readme
2024-10-17 18:34:20 +03:00
Aine
19302466ef update README.md 2024-10-10 22:13:50 +03:00
Aine
0594259ae4 make avatar field sortable in the users list 2024-10-08 12:15:03 +03:00
Aine
ba485bbb18 Merge branch 'master' 2024-10-08 10:51:16 +03:00
Dirk Klimpel
9fc005032c Fix for empty user default tab after creation (#628) 2024-10-08 09:20:55 +02:00
Aine
f5d6f24b30 correctly treat empty or "almost empty" restrictBaseUrl config option, fixes #56 2024-10-07 12:00:33 +03:00
Aine
a42efe7eda do not color failed federation destinations, use an icon instead 2024-10-04 00:16:11 +03:00
39 changed files with 1731 additions and 1340 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10

View File

@@ -25,10 +25,6 @@ jobs:
cache: yarn
- name: Install dependencies
run: yarn install --immutable --network-timeout=300000
- name: Set version into manifest.json
run: |
TAG=$(git describe --tags --abbrev=0 || echo "latest")
sed -i "s|\"icons\"|\"version\": \"$TAG\",\\n \"icons\"|g" public/manifest.json
- name: Build
run: yarn build --base=${{ env.base_path }}
- uses: actions/upload-artifact@v4
@@ -52,7 +48,7 @@ jobs:
name: dist
path: dist/
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:

View File

@@ -1,4 +1,16 @@
# Synapse Admin UI [![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
<p align="center">
<img alt="Synapse Admin Logo" src="./public/images/logo.webp" height="140" />
<h3 align="center">
Synapse Admin<br>
<a href="https://matrix.to/#/#synapse-admin:etke.cc">
<img alt="Community room" src="https://img.shields.io/badge/room-community_room-green?logo=matrix&label=%23synapse-admin%3Aetke.cc">
</a><br>
<img alt="License" src="https://img.shields.io/github/license/etkecc/synapse-admin">
</h3>
<p align="center">Manager your Synapse homeserver with ease</p>
</p>
---
![Screenshots](./screenshots.jpg)
@@ -7,12 +19,13 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
<!-- vim-markdown-toc GFM -->
* [Fork differences](#fork-differences)
* [Available via CDN](#available-via-cdn)
* [Availability](#availability)
* [Changes](#changes)
* [Development](#development)
* [Configuration](#configuration)
* [Restricting available homeserver](#restricting-available-homeserver)
* [Protecting appservice managed users](#protecting-appservice-managed-users)
* [Adding custom menu items](#adding-custom-menu-items)
* [Providing support URL](#providing-support-url)
* [Usage](#usage)
* [Supported Synapse](#supported-synapse)
@@ -22,7 +35,7 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
* [Steps for 1)](#steps-for-1)
* [Steps for 2)](#steps-for-2)
* [Steps for 3)](#steps-for-3)
* [Serving Synapse-Admin on a different path](#serving-synapse-admin-on-a-different-path)
* [Serving Synapse Admin on a different path](#serving-synapse-admin-on-a-different-path)
* [Development](#development-1)
<!-- vim-markdown-toc -->
@@ -33,9 +46,11 @@ With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologie
fork is intended to be a more feature-rich version of the original project. The main goal is to provide a more
user-friendly interface for managing Synapse homeservers.
### Available via CDN
### Availability
On [admin.etke.cc](https://admin.etke.cc) you can find the latest version of this fork.
* As a core/default component on [etke.cc](https://etke.cc/?utm_source=github&utm_medium=readme&utm_campaign=synapse-admin)
* Via CDN on [admin.etke.cc](https://admin.etke.cc)
* As a component in [Matrix-Docker-Ansible-Deploy Playbook](https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-playbook-synapse-admin.md)
### Changes
@@ -51,8 +66,9 @@ The following changes are already implemented:
* [Fix redirect URL after user creation](https://github.com/etkecc/synapse-admin/pull/16)
* [Display actual Synapse errors](https://github.com/etkecc/synapse-admin/pull/17)
* [Fix base_url being undefined on unsuccessful login](https://github.com/etkecc/synapse-admin/pull/18)
* [Put the version into manifest.json](https://github.com/Awesome-Technologies/synapse-admin/issues/507) (CI only)
* [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using theme colors)
* [Put the version into manifest.json](https://github.com/Awesome-Technologies/synapse-admin/issues/507) (later replaced
with a proper manifest.json generation on build)
* [Federation page improvements](https://github.com/Awesome-Technologies/synapse-admin/pull/583) (using icons)
* [Add UI option to block deleted rooms from being rejoined](https://github.com/etkecc/synapse-admin/pull/26)
* [Fix required fields check on Bulk registration CSV upload](https://github.com/etkecc/synapse-admin/pull/32)
* [Fix requests with invalid MXIDs on Bulk registration](https://github.com/etkecc/synapse-admin/pull/33)
@@ -63,6 +79,13 @@ The following changes are already implemented:
* [Provide options to delete media and redact events on user erase](https://github.com/etkecc/synapse-admin/pull/49)
* [Authenticated Media support](https://github.com/etkecc/synapse-admin/pull/51)
* [Better media preview/download](https://github.com/etkecc/synapse-admin/pull/53)
* [Login with access token](https://github.com/etkecc/synapse-admin/pull/58)
* [Fix footer causing vertical scrollbar](https://github.com/etkecc/synapse-admin/pull/60)
* [Custom Menu Items](https://github.com/etkecc/synapse-admin/pull/79)
* [Add user profile to the top menu](https://github.com/etkecc/synapse-admin/pull/80)
* [Enable visual customization](https://github.com/etkecc/synapse-admin/pull/81)
* [Fix room state events display](https://github.com/etkecc/synapse-admin/pull/100)
* [Sanitize CSV on import](https://github.com/etkecc/synapse-admin/pull/101)
_the list will be updated as new changes are added_
@@ -125,9 +148,29 @@ Example for [mautrix-telegram](https://github.com/mautrix/telegram)
}
```
### Adding custom menu items
You can add custom menu items to the main menu by providing a `menu` array in the `config.json`.
```json
{
"menu": [
{
"label": "Contact support",
"icon": "SupportAgent",
"url": "https://github.com/etkecc/synapse-admin/issues"
}
]
}
```
Where `icon` is one of the [preloaded icons](./src/components/icons.ts)
### Providing support URL
Synapse-Admin provides a support link in the main menu - `Contact support`. By default, the link points to the GitHub issues page of the project. You can change this link by providing a `supportURL` in the `config.json`.
**Deprecated**: use `menu` config option described above. Automatically migrated to the `menu` if the `supportURL` is present.
~~Synapse Admin provides a support link in the main menu - `Contact support`. By default, the link points to the GitHub issues page of the project. You can change this link by providing a `supportURL` in the `config.json`.~~
```json
{
@@ -214,7 +257,7 @@ You have three options:
- browse to http://localhost:8080
### Serving Synapse-Admin on a different path
### Serving Synapse Admin on a different path
The path prefix where synapse-admin is served can only be changed during the build step.
@@ -222,7 +265,7 @@ If you downloaded the source code, use `yarn build --base=/my-prefix` to set a p
If you want to build your own Docker container, use the `BASE_PATH` argument.
We do not support directly changing the path where Synapse-Admin is served in the pre-built Docker container. Instead please use a reverse proxy if you need to move Synapse-Admin to a different base path. If you want to serve multiple applications with different paths on the same domain, you need a reverse proxy anyway.
We do not support directly changing the path where Synapse Admin is served in the pre-built Docker container. Instead please use a reverse proxy if you need to move Synapse Admin to a different base path. If you want to serve multiple applications with different paths on the same domain, you need a reverse proxy anyway.
Example for Traefik:

View File

@@ -4,17 +4,14 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Synapse-Admin"
/>
<meta name="description" content="Synapse Admin" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="./manifest.json" />
<link rel="shortcut icon" href="./favicon.ico" />
<title>Synapse-Admin</title>
<title>Synapse Admin</title>
<style>
body {
margin: 0;
@@ -22,6 +19,11 @@
font-family: sans-serif;
}
.layout {
min-height: 90vh !important;
height: 90vh;
}
.loader-container {
display: flex;
align-items: center;
@@ -120,13 +122,7 @@
</div>
</div>
<script type="module" src="/src/index.tsx"></script>
<footer
style="position: relative; z-index: 2; height: 2em; margin-top: 0; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
<a id="copyright" href="https://github.com/etkecc/synapse-admin"
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
Synapse-Admin <b><span id="version"></span></b> by Awesome Technologies Innovationslabor GmbH
</a>
</footer>
<span id="js-version" style="display: none;"></span>
</body>
<script>document.getElementById("version").textContent = __SYNAPSE_ADMIN_VERSION__</script>
<script>document.getElementById("js-version").textContent = __SYNAPSE_ADMIN_VERSION__</script>
</html>

View File

@@ -25,11 +25,15 @@ run-dev:
stop-dev:
@docker-compose -f docker-compose-dev.yml stop
# register a user in the dev stack
register-user localpart password *admin:
docker-compose exec synapse register_new_matrix_user {{ if admin == "1" {"--admin"} else {"--no-admin"} }} -u {{ localpart }} -p {{ password }} -c /config/homeserver.yaml http://localhost:8008
# run yarn {fix,lint,test} commands
test:
@-yarn run fix
@-yarn run lint
@-yarn run test
# run the app in a production mode
run-prod: build

View File

@@ -3,7 +3,7 @@
"version": "0.10.3",
"description": "Admin GUI for the Matrix.org server Synapse",
"type": "module",
"author": "Awesome Technologies Innovationslabor GmbH",
"author": "etke.cc (originally by Awesome Technologies Innovationslabor GmbH)",
"license": "Apache-2.0",
"homepage": ".",
"repository": {
@@ -11,25 +11,25 @@
"url": "https://github.com/etkecc/synapse-admin"
},
"devDependencies": {
"@eslint/js": "^9.7.0",
"@eslint/js": "^9.13.0",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.7",
"@types/node": "^20.14.12",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.3.3",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.12",
"@types/node": "^22.8.1",
"@types/papaparse": "^5.3.15",
"@types/react": "^18.3.12",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"@typescript-eslint/parser": "^8.11.0",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unused-imports": "^3.2.0",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-yaml": "^1.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@@ -38,41 +38,42 @@
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.16.1",
"vite": "^5.4.6",
"vite-plugin-version-mark": "^0.1.0"
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vite-plugin-version-mark": "^0.1.2"
},
"dependencies": {
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@haleos/ra-language-german": "^1.0.0",
"@haxqer/ra-language-chinese": "^4.16.2",
"@mui/icons-material": "^6.1.1",
"@mui/material": "^6.1.1",
"@tanstack/react-query": "^5.56.2",
"@mui/icons-material": "^6.1.5",
"@mui/material": "^6.1.5",
"@mui/utils": "^5.16.6",
"@tanstack/react-query": "^5.59.16",
"history": "^5.3.0",
"lodash": "^4.17.21",
"papaparse": "^5.4.1",
"ra-core": "^5.2.0",
"ra-i18n-polyglot": "^5.2.0",
"ra-language-english": "^5.2.0",
"ra-core": "^5.3.1",
"ra-i18n-polyglot": "^5.3.0",
"ra-language-english": "^5.3.1",
"ra-language-farsi": "^5.0.0",
"ra-language-french": "^5.2.0",
"ra-language-french": "^5.3.1",
"ra-language-italian": "^3.13.1",
"ra-language-russian": "^4.14.2",
"react": "^18.3.1",
"react-admin": "^5.2.0",
"react-admin": "^5.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.53.1",
"react-is": "^18.3.1",
"react-router": "^6.26.2",
"react-router-dom": "^6.26.2"
"react-router-dom": "^6.27.0"
},
"scripts": {
"start": "vite serve",
"build": "vite build",
"lint": "eslint --ignore-path .gitignore --ext .ts,.tsx,.yml,.yaml .",
"lint": "ESLINT_USE_FLAT_CONFIG=false eslint --ignore-path .gitignore --ignore-pattern testdata/ --ext .ts,.tsx,.yml,.yaml .",
"fix": "yarn lint --fix",
"test": "yarn jest",
"test:watch": "yarn jest --watch"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/images/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,15 +0,0 @@
{
"short_name": "Synapse-Admin",
"name": "Synapse-Admin",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,4 +1,4 @@
import { render, screen, waitFor } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import fetchMock from "jest-fetch-mock";
fetchMock.enableMocks();
@@ -9,4 +9,4 @@ describe("App", () => {
render(<App />);
await screen.findAllByText("Welcome to Synapse-admin");
});
});
});

View File

@@ -23,6 +23,7 @@ import users from "./resources/users";
import authProvider from "./synapse/authProvider";
import dataProvider from "./synapse/dataProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Footer from "./components/Footer";
// TODO: Can we use lazy loading together with browser locale?
const messages = {
@@ -81,6 +82,7 @@ const App = () => (
<Resource name="room_state" />
<Resource name="destination_rooms" />
</Admin>
<Footer />
</QueryClientProvider>
);

View File

@@ -4,6 +4,13 @@ interface AppContextType {
restrictBaseUrl: string | string[];
asManagedUsers: string[];
supportURL: string;
menu: MenuItem[];
}
interface MenuItem {
label: string;
icon: string;
url: string;
}
export const AppContext = createContext({});

View File

@@ -1,26 +1,103 @@
import { Layout, Menu } from 'react-admin';
import LiveHelpIcon from '@mui/icons-material/LiveHelp';
import { AppBar, TitlePortal, InspectorButton, Confirm, Layout, Logout, Menu, useLogout, UserMenu } from "react-admin";
import { LoginMethod } from "../pages/LoginPage";
import { useEffect, useState, Suspense } from "react";
import { Icons, DefaultIcon } from "./icons";
const DEFAULT_SUPPORT_LINK = "https://github.com/etkecc/synapse-admin/issues";
const supportLink = (): string => {
try {
new URL(localStorage.getItem("support_url") || ''); // Check if the URL is valid
return localStorage.getItem("support_url") || DEFAULT_SUPPORT_LINK;
} catch (e) {
return DEFAULT_SUPPORT_LINK;
const AdminUserMenu = () => {
const [open, setOpen] = useState(false);
const logout = useLogout();
const checkLoginType = (ev: React.MouseEvent<HTMLDivElement>) => {
const loginType: LoginMethod = (localStorage.getItem("login_type") || "credentials") as LoginMethod;
if (loginType === "accessToken") {
ev.stopPropagation();
setOpen(true);
}
};
const handleConfirm = () => {
setOpen(false);
logout();
};
const handleDialogClose = () => {
setOpen(false);
localStorage.removeItem("access_token");
localStorage.removeItem("login_type");
window.location.reload();
};
return (
<UserMenu>
<div onClickCapture={checkLoginType}>
<Logout />
</div>
<Confirm
isOpen={open}
title="synapseadmin.auth.logout_acces_token_dialog.title"
content="synapseadmin.auth.logout_acces_token_dialog.content"
onConfirm={handleConfirm}
onClose={handleDialogClose}
confirm="synapseadmin.auth.logout_acces_token_dialog.confirm"
cancel="synapseadmin.auth.logout_acces_token_dialog.cancel"
/>
</UserMenu>
);
};
const AdminAppBar = () => {
return (<AppBar userMenu={<AdminUserMenu />}>
<TitlePortal />
<InspectorButton />
</AppBar>);
};
const AdminMenu = () => (
<Menu>
<Menu.ResourceItems />
<Menu.Item to={supportLink()} target="_blank" primaryText="Contact support" leftIcon={<LiveHelpIcon />} />
const AdminMenu = (props) => {
const [menu, setMenu] = useState([]);
useEffect(() => {
const menuConfig = localStorage.getItem('menu');
if (menuConfig) {
try {
setMenu(JSON.parse(menuConfig));
} catch (e) {
console.error('Error parsing menu configuration', e);
}
}
}, []);
return (
<Menu {...props}>
<Menu.ResourceItems />
{menu.map((item, index) => {
const { url, icon, label } = item;
const IconComponent = Icons[icon] as React.ComponentType<any> | undefined;
return (
<Suspense key={index}>
<Menu.Item
to={url}
target="_blank"
primaryText={label}
leftIcon={IconComponent ? <IconComponent /> : <DefaultIcon />}
onClick={props.onMenuClick}
/>
</Suspense>
);
})}
</Menu>
);
);
};
export const AdminLayout = ({ children }) => (
<Layout menu={AdminMenu}>
{children}
</Layout>
<Layout appBar={AdminAppBar} menu={AdminMenu} sx={{
['& .RaLayout-appFrame']: {
minHeight: '90vh',
height: '90vh',
},
['& .RaLayout-content']: {
marginBottom: '3rem',
},
}}>
{children}
</Layout>
);

View File

@@ -1,12 +1,11 @@
import { get } from "lodash";
import { Avatar, AvatarProps } from "@mui/material";
import { useRecordContext } from "react-admin";
import { FieldProps, useRecordContext } from "react-admin";
import { useState, useEffect, useCallback } from "react";
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
const AvatarField = ({ source, ...rest }: AvatarProps & { source: string, label?: string }) => {
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
const { alt, classes, sizes, sx, variant } = rest;
const record = useRecordContext(rest);
const mxcURL = get(record, source)?.toString();

41
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,41 @@
import { Avatar, Box, Link, Typography } from "@mui/material";
import { useEffect, useState } from "react";
const Footer = () => {
const [version, setVersion] = useState<string | null>(null);
useEffect(() => {
const version = document.getElementById("js-version")?.textContent;
if (version) {
setVersion(version);
}
}, []);
return (<Box
component="footer"
sx={{
position: 'fixed',
zIndex: 100,
bottom: 0,
width: '100%',
bgcolor: "#eee",
borderTop: '1px solid',
borderColor: '#ddd',
p: 1,
}}>
<Typography variant="body2">
<Avatar src="./images/logo.webp" sx={{ width: "1rem", height: "1rem", display: "inline-block", verticalAlign: "sub" }} />
<Link sx={{ color: "#888", textDecoration: 'none' }} href="https://github.com/etkecc/synapse-admin" target="_blank">
Synapse Admin
</Link> <Link href={`https://github.com/etkecc/synapse-admin/releases/tag/`+version} target="_blank">
<span style={{ fontWeight: 'bold', color: "#000" }}>{version}</span>
</Link> <Link sx={{ color: "#888", textDecoration: 'none' }} href="https://etke.cc/?utm_source=synapse-admin&utm_medium=footer&utm_campaign=synapse-admin" target="_blank">
by etke.cc
</Link> <Link sx={{ color: "#888", textDecoration: 'none' }} href="https://github.com/awesome-technologies/synapse-admin" target="_blank">
(originally developed by Awesome Technologies Innovationslabor GmbH).
</Link> <Link sx={{ fontWeight: 'bold', color: "#000", textDecoration: 'none' }} href="https://matrix.to/#/#synapse-admin:etke.cc" target="_blank">#synapse-admin:etke.cc</Link>
</Typography>
</Box>
);
};
export default Footer;

View File

@@ -121,11 +121,8 @@ const FilePicker = () => {
const verifyCsv = ({ data, meta, errors }: ParseResult<ImportLine>, { setValues, setStats, setError }) => {
/* First, verify the presence of required fields */
const missingFields = expectedFields.filter(eF => {
const result = meta.fields?.find(mF => eF === mF);
if (result === undefined) { return eF; } // missing field
return undefined; // field found
});
meta.fields = meta.fields?.map(f => f.trim().toLowerCase());
const missingFields = expectedFields.filter(eF => !meta.fields?.find(mF => eF === mF));
if (missingFields.length > 0) {
setError(translate("import_users.error.required_field", { field: missingFields[0] }));
@@ -151,6 +148,15 @@ const FilePicker = () => {
};
const errorMessages = errors.map(e => e.message);
// sanitize the data first
data = data.map(line => {
const newLine = {} as ImportLine;
for (const [key, value] of Object.entries(line)) {
newLine[key.trim().toLowerCase()] = value;
}
return newLine;
});
// process the data
data.forEach((line, idx) => {
if (line.user_type === undefined || line.user_type === "") {
stats.user_types.default++;
@@ -177,6 +183,7 @@ const FilePicker = () => {
line[f] = true; // we need true booleans instead of strings
} else {
if (line[f] !== "false" && line[f] !== "") {
console.log("invalid value", line[f], "for field " + f + " in row " + idx);
errorMessages.push(
translate("import_users.error.invalid_value", {
field: f,

View File

@@ -0,0 +1,58 @@
import { styled } from "@mui/material/styles";
import { Box } from "@mui/material";
const LoginFormBox = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
minHeight: "calc(100vh - 1rem)",
alignItems: "center",
justifyContent: "flex-start",
background: "url(./images/floating-cogs.svg)",
backgroundColor: "#f9f9f9",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
[`& .card`]: {
width: "30rem",
marginTop: "6rem",
marginBottom: "6rem",
},
[`& .avatar`]: {
margin: "1rem",
display: "flex",
justifyContent: "center",
},
[`& .icon`]: {
backgroundColor: theme.palette.grey[500],
},
[`& .hint`]: {
marginTop: "1em",
marginBottom: "1em",
display: "flex",
justifyContent: "center",
color: theme.palette.grey[600],
},
[`& .form`]: {
padding: "0 1rem 1rem 1rem",
},
[`& .select`]: {
marginBottom: "2rem",
},
[`& .actions`]: {
padding: "0 1rem 1rem 1rem",
},
[`& .serverVersion`]: {
color: theme.palette.grey[500],
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
marginLeft: "0.5rem",
},
[`& .matrixVersions`]: {
color: theme.palette.grey[500],
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
fontSize: "0.8rem",
marginBottom: "1rem",
marginLeft: "0.5rem",
},
}));
export default LoginFormBox;

12
src/components/icons.ts Normal file
View File

@@ -0,0 +1,12 @@
import { lazy } from "react";
export const Icons = {
Announcement: lazy(() => import('@mui/icons-material/Announcement')),
Engineering: lazy(() => import('@mui/icons-material/Engineering')),
HelpCenter: lazy(() => import('@mui/icons-material/HelpCenter')),
SupportAgent: lazy(() => import('@mui/icons-material/SupportAgent')),
Default: lazy(() => import('@mui/icons-material/OpenInNew')),
// Add more icons as needed
};
export const DefaultIcon = Icons.Default;

View File

@@ -2,7 +2,7 @@ import { formalGermanMessages } from "@haleos/ra-language-german";
import { SynapseTranslationMessages } from ".";
const de: SynapseTranslationMessages = {
const fixedGermanMessages = {
...formalGermanMessages,
ra: {
...formalGermanMessages.ra,
@@ -10,8 +10,30 @@ const de: SynapseTranslationMessages = {
...formalGermanMessages.ra.navigation,
no_filtered_results: "Keine Ergebnisse",
clear_filters: "Alle Filter entfernen",
add_filter: "Filter hinzufügen",
},
action: {
...formalGermanMessages.ra.action,
update_application: "Anwendung aktualisieren",
},
page: {
...formalGermanMessages.ra.page,
empty: "Leer",
access_denied: "Zugriff verweigert",
authentication_error: "Authentifizierungsfehler",
},
message: {
...formalGermanMessages.ra.message,
access_denied:
"Sie haben nicht die richtigen Berechtigungen um auf diese Seite zuzugreifen.",
authentication_error:
"Der Authentifizierungsserver hat einen Fehler zurückgegeben und Ihre Anmeldedaten konnten nicht überprüft werden.",
},
},
}
const de: SynapseTranslationMessages = {
...fixedGermanMessages,
synapseadmin: {
auth: {
base_url: "Heimserver URL",
@@ -22,6 +44,14 @@ const de: SynapseTranslationMessages = {
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
url_error: "Keine gültige Matrix Server URL",
sso_sign_in: "Anmeldung mit SSO",
credentials: "Anmeldedaten",
access_token: "Zugriffstoken",
logout_acces_token_dialog: {
title: "Sie verwenden ein bestehendes Matrix-Zugriffstoken.",
content: "Möchten Sie diese Sitzung (die anderswo, z.B. in einem Matrix-Client, verwendet werden könnte) beenden oder sich nur vom Admin-Panel abmelden?",
confirm: "Sitzung beenden",
cancel: "Nur vom Admin-Panel abmelden",
},
},
users: {
invalid_user_id: "Lokaler Anteil der Matrix Benutzer-ID ohne Homeserver.",

View File

@@ -14,6 +14,14 @@ const en: SynapseTranslationMessages = {
protocol_error: "URL has to start with 'http://' or 'https://'",
url_error: "Not a valid Matrix server URL",
sso_sign_in: "Sign in with SSO",
credentials: "Credentials",
access_token: "Access token",
logout_acces_token_dialog: {
title: "You are using an existing Matrix access token.",
content: "Do you want to destroy this session (that could be used elsewhere, e.g. in a Matrix client) or just logout from the admin panel?",
confirm: "Destroy session",
cancel: "Just logout from admin panel",
},
},
users: {
invalid_user_id: "Localpart of a Matrix user-id without homeserver.",

View File

@@ -13,6 +13,14 @@ const fa: SynapseTranslationMessages = {
protocol_error: "URL باید با 'http://' یا 'https://' شروع شود",
url_error: "آدرس وارد شده یک سرور معتبر نیست",
sso_sign_in: "با SSO وارد شوید",
credentials: "اعتبارنامه",
access_token: "توکن دسترسی",
logout_acces_token_dialog: {
title: "شما در حال استفاده از یک نشانه دسترسی ماتریکس موجود هستید.",
content: "آیا می‌خواهید این جلسه (که می‌تواند در جای دیگر، مانند یک کلاینت ماتریکس استفاده شود) را نابود کنید یا فقط از پنل مدیریت خارج شوید؟",
confirm: "نابودی جلسه",
cancel: "فقط خروج از پنل مدیریت",
},
},
users: {
invalid_user_id: "بخش محلی یک شناسه کاربری ماتریکس بدون سرور خانگی.",

View File

@@ -13,6 +13,14 @@ const fr: SynapseTranslationMessages = {
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",
credentials: "Identifiants",
access_token: "Jeton d'accès",
logout_acces_token_dialog: {
title: "Vous utilisez un jeton d'accès Matrix existant.",
content: "Voulez-vous détruire cette session (qui pourrait être utilisée ailleurs, par exemple dans un client Matrix) ou simplement vous déconnecter du panneau d'administration?",
confirm: "Détruire la session",
cancel: "Se déconnecter simplement du panneau d'administration",
},
},
users: {
invalid_user_id: "Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur daccueil.",
@@ -201,9 +209,9 @@ const fr: SynapseTranslationMessages = {
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 !",
fields: {
block: "Bloquer et empêcher les utilisateurs de rejoindre la salle",
},
fields: {
block: "Bloquer et empêcher les utilisateurs de rejoindre la salle",
},
success: "Salle/s supprimées avec succès.",
failure: "La/les salle/s n'ont pas pu être supprimées.",
},

8
src/i18n/index.d.ts vendored
View File

@@ -11,6 +11,14 @@ interface SynapseTranslationMessages extends TranslationMessages {
protocol_error: string;
url_error: string;
sso_sign_in: string;
credentials: string;
access_token: string;
logout_acces_token_dialog: {
title: string;
content: string;
confirm: string;
cancel: string;
};
};
users: {
invalid_user_id: string;

View File

@@ -13,6 +13,14 @@ const it: SynapseTranslationMessages = {
protocol_error: "L'URL deve iniziare per 'http://' o 'https://'",
url_error: "URL del server Matrix non valido",
sso_sign_in: "Accedi con SSO",
credentials: "Credenziali",
access_token: "Token di accesso",
logout_acces_token_dialog: {
title: "Stai utilizzando un token di accesso Matrix esistente.",
content: "Vuoi distruggere questa sessione (che potrebbe essere utilizzata altrove, ad esempio in un client Matrix) o semplicemente disconnetterti dal pannello di amministrazione?",
confirm: "Distruggi sessione",
cancel: "Disconnetti solo dal pannello di amministrazione",
},
},
users: {
invalid_user_id: "ID utente non valido su questo homeserver.",
@@ -174,211 +182,211 @@ const it: SynapseTranslationMessages = {
},
helper: {
/* forward_extremities:
"Forward extremities are the leaf events at the end of a Directed acyclic graph (DAG) in a room, aka events that have no children. The more exist in a room, the more state resolution that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again. If a room has >10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in #1760.", */
},
enums: {
"Forward extremities are the leaf events at the end of a Directed acyclic graph (DAG) in a room, aka events that have no children. The more exist in a room, the more state resolution that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again. If a room has >10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in #1760.", */
},
enums: {
join_rules: {
public: "Pubblica",
knock: "Bussa",
invite: "Invita",
private: "Privata",
},
guest_access: {
can_join: "Gli utenti ospiti possono entrare",
forbidden: "Gli utenti ospiti non possono entrare",
},
history_visibility: {
invited: "Dall'invito",
joined: "Dall'entrata",
shared: "Dalla condivisione",
world_readable: "Chiunque",
},
unencrypted: "Non criptata",
public: "Pubblica",
knock: "Bussa",
invite: "Invita",
private: "Privata",
},
action: {
erase: {
title: "Cancella stanza",
content:
"Sei sicuro di voler eliminare questa stanza? Questa azione è definitiva. Tutti i messaggi e i media condivisi in questa stanza verranno eliminati dal server!",
},
guest_access: {
can_join: "Gli utenti ospiti possono entrare",
forbidden: "Gli utenti ospiti non possono entrare",
},
history_visibility: {
invited: "Dall'invito",
joined: "Dall'entrata",
shared: "Dalla condivisione",
world_readable: "Chiunque",
},
unencrypted: "Non criptata",
},
reports: {
name: "Evento segnalato |||| Eventi segnalati",
fields: {
id: "ID",
received_ts: "Orario del report",
user_id: "richiedente",
name: "nome della stanza",
score: "punteggio",
reason: "ragione",
event_id: "ID dell'evento",
event_json: {
origin: "server di origine",
origin_server_ts: "ora dell'invio",
type: "tipo di evento",
content: {
msgtype: "tipo di contenuto",
body: "contenuto",
format: "formato",
formatted_body: "contenuto formattato",
algorithm: "algoritmo",
},
},
},
},
connections: {
name: "Connessioni",
fields: {
last_seen: "Data",
ip: "Indirizzo IP",
user_agent: "agente utente",
},
},
devices: {
name: "Dispositivo |||| Dispositivi",
fields: {
device_id: "ID del dispositivo",
display_name: "Nome del dispositivo",
last_seen_ts: "Timestamp",
last_seen_ip: "Indirizzo IP",
},
action: {
erase: {
title: "Rimozione del dispositivo %{id}",
content: 'Sei sicuro di voler rimuovere il dispositivo "%{name}"?',
success: "Dispositivo rimosso con successo.",
failure: "C'è stato un errore.",
},
},
},
users_media: {
name: "Media",
fields: {
media_id: "ID del media",
media_length: "Peso del file (in Byte)",
media_type: "Tipo",
upload_name: "Nome del file",
quarantined_by: "In quarantena da",
safe_from_quarantine: "Protetto dalla quarantena",
created_ts: "Creato",
last_access_ts: "Ultimo accesso",
},
},
protect_media: {
action: {
create: "Non protetto, proteggi",
delete: "Protetto, rimuovi protezione",
none: "In quarantena",
send_success: "Stato della protezione cambiato con successo.",
send_failure: "C'è stato un errore.",
},
},
quarantine_media: {
action: {
name: "Quarantina",
create: "Aggiungi alla quarantena",
delete: "In quarantena, rimuovi dalla quarantena",
none: "Protetto dalla quarantena",
send_success: "Stato della quarantena cambiato con successo.",
send_failure: "C'è stato un errore.",
},
},
pushers: {
name: "Pusher |||| Pusher",
fields: {
app: "App",
app_display_name: "Nome dell'app",
app_id: "ID dell'app",
device_display_name: "Nome del dispositivo",
kind: "Tipo",
lang: "Lingua",
profile_tag: "Tag del profilo",
pushkey: "Pushkey",
data: { url: "URL" },
},
},
servernotices: {
name: "Avvisi del server",
send: "Invia avvisi",
fields: {
body: "Messaggio",
},
action: {
send: "Invia nota",
send_success: "Avviso inviato con successo.",
send_failure: "C'è stato un errore.",
},
helper: {
send: 'Invia un avviso dal server agli utenti selezionati. La feature "Avvisi del server" è stata attivata sul server.',
},
},
user_media_statistics: {
name: "Media degli utenti",
fields: {
media_count: "Numero media",
media_length: "Lunghezza media",
},
},
forward_extremities: {
name: "Invia estremità",
fields: {
id: "Event ID",
received_ts: "Timestamp",
depth: "Profondità",
state_group: "State group",
},
},
room_state: {
name: "Eventi di stato",
fields: {
type: "Tipo",
content: "Contenuto",
origin_server_ts: "Ora dell'invio",
sender: "Mittente",
},
},
room_directory: {
name: "Elenco delle stanze",
fields: {
world_readable: "gli utenti ospite possono vedere senza entrare",
guest_can_join: "gli utenti ospite possono entrare",
},
action: {
title: "Cancella stanza dall'elenco |||| Cancella %{smart_count} stanze dall'elenco",
action: {
erase: {
title: "Cancella stanza",
content:
"Sei sicuro di voler rimuovere questa stanza dall'elenco? |||| Sei sicuro di voler rimuovere %{smart_count} stanze dall'elenco?",
erase: "Rimuovi dall'elenco",
create: "Crea",
send_success: "Stanza creata con successo.",
send_failure: "C'è stato un errore.",
"Sei sicuro di voler eliminare questa stanza? Questa azione è definitiva. Tutti i messaggi e i media condivisi in questa stanza verranno eliminati dal server!",
},
},
destinations: {
name: "Federazione",
fields: {
destination: "Destinazione",
failure_ts: "Timestamp dell'errore",
retry_last_ts: "Tentativo ultimo timestamp",
retry_interval: "Intervallo dei tentativi",
last_successful_stream_ordering: "Ultimo flusso riuscito con successo",
stream_ordering: "Flusso",
},
reports: {
name: "Evento segnalato |||| Eventi segnalati",
fields: {
id: "ID",
received_ts: "Orario del report",
user_id: "richiedente",
name: "nome della stanza",
score: "punteggio",
reason: "ragione",
event_id: "ID dell'evento",
event_json: {
origin: "server di origine",
origin_server_ts: "ora dell'invio",
type: "tipo di evento",
content: {
msgtype: "tipo di contenuto",
body: "contenuto",
format: "formato",
formatted_body: "contenuto formattato",
algorithm: "algoritmo",
},
},
action: { reconnect: "Riconnetti" },
},
registration_tokens: {
name: "Token di registrazione",
fields: {
token: "Token",
valid: "Token valido",
uses_allowed: "Usi permessi",
pending: "In attesa",
completed: "Completato",
expiry_time: "Data della scadenza",
length: "Lunghezza",
},
connections: {
name: "Connessioni",
fields: {
last_seen: "Data",
ip: "Indirizzo IP",
user_agent: "agente utente",
},
},
devices: {
name: "Dispositivo |||| Dispositivi",
fields: {
device_id: "ID del dispositivo",
display_name: "Nome del dispositivo",
last_seen_ts: "Timestamp",
last_seen_ip: "Indirizzo IP",
},
action: {
erase: {
title: "Rimozione del dispositivo %{id}",
content: 'Sei sicuro di voler rimuovere il dispositivo "%{name}"?',
success: "Dispositivo rimosso con successo.",
failure: "C'è stato un errore.",
},
helper: { length: "Lunghezza del token se non viene dato alcun token." },
},
},
users_media: {
name: "Media",
fields: {
media_id: "ID del media",
media_length: "Peso del file (in Byte)",
media_type: "Tipo",
upload_name: "Nome del file",
quarantined_by: "In quarantena da",
safe_from_quarantine: "Protetto dalla quarantena",
created_ts: "Creato",
last_access_ts: "Ultimo accesso",
},
},
protect_media: {
action: {
create: "Non protetto, proteggi",
delete: "Protetto, rimuovi protezione",
none: "In quarantena",
send_success: "Stato della protezione cambiato con successo.",
send_failure: "C'è stato un errore.",
},
},
quarantine_media: {
action: {
name: "Quarantina",
create: "Aggiungi alla quarantena",
delete: "In quarantena, rimuovi dalla quarantena",
none: "Protetto dalla quarantena",
send_success: "Stato della quarantena cambiato con successo.",
send_failure: "C'è stato un errore.",
},
},
pushers: {
name: "Pusher |||| Pusher",
fields: {
app: "App",
app_display_name: "Nome dell'app",
app_id: "ID dell'app",
device_display_name: "Nome del dispositivo",
kind: "Tipo",
lang: "Lingua",
profile_tag: "Tag del profilo",
pushkey: "Pushkey",
data: { url: "URL" },
},
},
servernotices: {
name: "Avvisi del server",
send: "Invia avvisi",
fields: {
body: "Messaggio",
},
action: {
send: "Invia nota",
send_success: "Avviso inviato con successo.",
send_failure: "C'è stato un errore.",
},
helper: {
send: 'Invia un avviso dal server agli utenti selezionati. La feature "Avvisi del server" è stata attivata sul server.',
},
},
user_media_statistics: {
name: "Media degli utenti",
fields: {
media_count: "Numero media",
media_length: "Lunghezza media",
},
},
forward_extremities: {
name: "Invia estremità",
fields: {
id: "Event ID",
received_ts: "Timestamp",
depth: "Profondità",
state_group: "State group",
},
},
room_state: {
name: "Eventi di stato",
fields: {
type: "Tipo",
content: "Contenuto",
origin_server_ts: "Ora dell'invio",
sender: "Mittente",
},
},
room_directory: {
name: "Elenco delle stanze",
fields: {
world_readable: "gli utenti ospite possono vedere senza entrare",
guest_can_join: "gli utenti ospite possono entrare",
},
action: {
title: "Cancella stanza dall'elenco |||| Cancella %{smart_count} stanze dall'elenco",
content:
"Sei sicuro di voler rimuovere questa stanza dall'elenco? |||| Sei sicuro di voler rimuovere %{smart_count} stanze dall'elenco?",
erase: "Rimuovi dall'elenco",
create: "Crea",
send_success: "Stanza creata con successo.",
send_failure: "C'è stato un errore.",
},
},
destinations: {
name: "Federazione",
fields: {
destination: "Destinazione",
failure_ts: "Timestamp dell'errore",
retry_last_ts: "Tentativo ultimo timestamp",
retry_interval: "Intervallo dei tentativi",
last_successful_stream_ordering: "Ultimo flusso riuscito con successo",
stream_ordering: "Flusso",
},
action: { reconnect: "Riconnetti" },
},
registration_tokens: {
name: "Token di registrazione",
fields: {
token: "Token",
valid: "Token valido",
uses_allowed: "Usi permessi",
pending: "In attesa",
completed: "Completato",
expiry_time: "Data della scadenza",
length: "Lunghezza",
},
helper: { length: "Lunghezza del token se non viene dato alcun token." },
},
},
};
export default it;

View File

@@ -2,7 +2,7 @@ import russianMessages from "ra-language-russian";
import { SynapseTranslationMessages } from ".";
const ru: SynapseTranslationMessages = {
const fixedRussianMessages = {
...russianMessages,
ra: {
...russianMessages.ra,
@@ -11,7 +11,24 @@ const ru: SynapseTranslationMessages = {
no_filtered_results: "Нет результатов",
clear_filters: "Все фильтры сбросить",
},
page: {
...russianMessages.ra.page,
empty: "Пусто",
access_denied: "Доступ запрещен",
authentication_error: "Ошибка аутентификации",
},
message: {
...russianMessages.ra.message,
access_denied:
"У вас нет прав доступа к этой странице.",
authentication_error:
"Сервер аутентификации вернул ошибку и не смог проверить ваши учетные данные.",
},
},
}
const ru: SynapseTranslationMessages = {
...fixedRussianMessages,
synapseadmin: {
auth: {
base_url: "Адрес домашнего сервера",
@@ -22,6 +39,14 @@ const ru: SynapseTranslationMessages = {
protocol_error: "Адрес должен начинаться с 'http://' или 'https://'",
url_error: "Неверный адрес сервера Matrix",
sso_sign_in: "Вход через SSO",
credentials: "Учетные данные",
access_token: "Токен доступа",
logout_acces_token_dialog: {
title: "Вы используете существующий токен доступа Matrix.",
content: "Вы хотите завершить эту сессию (которая может быть использована в другом месте, например, в клиенте Matrix) или просто выйти из панели администрирования?",
confirm: "Завершить сессию",
cancel: "Просто выйти из панели администрирования",
},
},
users: {
invalid_user_id: "Локальная часть ID пользователя Matrix без адреса домашнего сервера.",
@@ -87,7 +112,7 @@ const ru: SynapseTranslationMessages = {
header: "Загрузить CSV файл",
explanation:
"Здесь вы можете загрузить файл со значениями, разделёнными запятыми, которые будут использованы для создания или обновления данных пользователей. \
В файле должны быть поля 'id' и 'displayname'. Вы можете скачать и изменить файл-образец отсюда: ",
В файле должны быть поля 'id' и 'displayname'. Вы можете скачать и изменить файл-образец отсюда: ",
},
startImport: {
simulate_only: "Только симулировать",
@@ -122,10 +147,10 @@ const ru: SynapseTranslationMessages = {
helper: {
send: "Это API удаляет локальные файлы с вашего собственного сервера, включая локальные миниатюры и копии скачанных файлов. \
Данный API не затрагивает файлы, загруженные во внешние хранилища.",
},
},
resources: {
users: {
},
},
resources: {
users: {
name: "Пользователь |||| Пользователи",
email: "Почта",
msisdn: "Телефон",
@@ -169,258 +194,258 @@ const ru: SynapseTranslationMessages = {
delete_media: "Удаление всех медиафайлов, загруженных пользователем (-ами)",
redact_events: "Удаление всех событий, отправленных пользователем (-ами)",
},
},
rooms: {
name: "Комната |||| Комнаты",
fields: {
room_id: "ID комнаты",
name: "Название",
canonical_alias: "Псевдоним",
joined_members: "Участники",
joined_local_members: "Локальные участники",
joined_local_devices: "Локальные устройства",
state_events: "События состояния / Сложность",
version: "Версия",
is_encrypted: "Зашифровано",
encryption: "Шифрование",
federatable: "Федерация",
public: "Отображается в каталоге комнат",
creator: "Создатель",
join_rules: "Правила входа",
guest_access: "Гостевой доступ",
history_visibility: "Видимость истории",
topic: "Тема",
avatar: "Аватар",
},
helper: {
forward_extremities:
"Оконечности — это события-листья в конце ориентированного ациклического графа (DAG) в комнате, т.е. события без дочерних элементов. \
rooms: {
name: "Комната |||| Комнаты",
fields: {
room_id: "ID комнаты",
name: "Название",
canonical_alias: "Псевдоним",
joined_members: "Участники",
joined_local_members: "Локальные участники",
joined_local_devices: "Локальные устройства",
state_events: "События состояния / Сложность",
version: "Версия",
is_encrypted: "Зашифровано",
encryption: "Шифрование",
federatable: "Федерация",
public: "Отображается в каталоге комнат",
creator: "Создатель",
join_rules: "Правила входа",
guest_access: "Гостевой доступ",
history_visibility: "Видимость истории",
topic: "Тема",
avatar: "Аватар",
},
helper: {
forward_extremities:
"Оконечности — это события-листья в конце ориентированного ациклического графа (DAG) в комнате, т.е. события без дочерних элементов. \
Чем больше их в комнате, тем больше Synapse работает над разрешением состояния (это дорогостоящая операция). \
Хотя Synapse старается не допускать существования слишком большого числа таких событий в комнате, из-за ошибок они иногда снова появляются. \
Если в комнате >10 оконечностей, стоит найти комнату-виновника и попробовать удалить их с помощью SQL-запросов из #1760.",
},
enums: {
join_rules: {
public: "Для всех",
knock: "Надо постучать",
invite: "По приглашению",
private: "Приватная",
},
guest_access: {
can_join: "Гости могут войти",
forbidden: "Гости не могут войти",
},
history_visibility: {
invited: "С момента приглашения",
joined: "С момента входа",
shared: "С момента открытия доступа",
world_readable: "Для всех",
},
unencrypted: "Без шифрования",
},
action: {
erase: {
title: "Удалить комнату",
content:
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
fields: {
block: "Заблокировать и запретить пользователям присоединяться к комнате",
enums: {
join_rules: {
public: "Для всех",
knock: "Надо постучать",
invite: "По приглашению",
private: "Приватная",
},
guest_access: {
can_join: "Гости могут войти",
forbidden: "Гости не могут войти",
},
history_visibility: {
invited: "С момента приглашения",
joined: "С момента входа",
shared: "С момента открытия доступа",
world_readable: "Для всех",
},
unencrypted: "Без шифрования",
},
action: {
erase: {
title: "Удалить комнату",
content:
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
fields: {
block: "Заблокировать и запретить пользователям присоединяться к комнате",
},
success: "Комната/ы успешно удалены",
failure: "Комната/ы не могут быть удалены.",
},
success: "Комната/ы успешно удалены",
failure: "Комната/ы не могут быть удалены.",
},
},
},
reports: {
name: "Жалоба |||| Жалобы",
fields: {
id: "ID",
received_ts: "Дата и время жалобы",
user_id: "Автор жалобы",
name: "Название комнаты",
score: "Баллы",
reason: "Причина",
event_id: "ID события",
event_json: {
origin: "Исходнный сервер",
origin_server_ts: "Дата и время отправки",
type: "Тип события",
content: {
msgtype: "Тип содержимого",
body: "Содержимое",
format: "Формат",
formatted_body: "Форматированное содержимое",
algorithm: "Алгоритм",
url: "Ссылка",
info: {
mimetype: "Тип",
reports: {
name: "Жалоба |||| Жалобы",
fields: {
id: "ID",
received_ts: "Дата и время жалобы",
user_id: "Автор жалобы",
name: "Название комнаты",
score: "Баллы",
reason: "Причина",
event_id: "ID события",
event_json: {
origin: "Исходнный сервер",
origin_server_ts: "Дата и время отправки",
type: "Тип события",
content: {
msgtype: "Тип содержимого",
body: "Содержимое",
format: "Формат",
formatted_body: "Форматированное содержимое",
algorithm: "Алгоритм",
url: "Ссылка",
info: {
mimetype: "Тип",
},
},
},
},
},
action: {
erase: {
title: "Удалить жалобу",
content: "Действительно удалить жалобу? Это действие будет невозможно отменить.",
action: {
erase: {
title: "Удалить жалобу",
content: "Действительно удалить жалобу? Это действие будет невозможно отменить.",
},
},
},
},
connections: {
name: "Подключения",
fields: {
last_seen: "Дата",
ip: "IP адрес",
user_agent: "Юзер-агент",
},
},
devices: {
name: "Устройство |||| Устройства",
fields: {
device_id: "ID устройства",
display_name: "Название",
last_seen_ts: "Дата и время",
last_seen_ip: "IP адрес",
},
action: {
erase: {
title: "Удаление %{id}",
content: 'Действительно удалить устройство "%{name}"?',
success: "Устройство успешно удалено.",
failure: "Произошла ошибка.",
connections: {
name: "Подключения",
fields: {
last_seen: "Дата",
ip: "IP адрес",
user_agent: "Юзер-агент",
},
},
},
users_media: {
name: "Файлы",
fields: {
media_id: "ID файла",
media_length: "Размер файла (в байтах)",
media_type: "Тип",
upload_name: "Имя файла",
quarantined_by: "На карантине",
safe_from_quarantine: "Защитить от карантина",
created_ts: "Создано",
last_access_ts: "Последний доступ",
devices: {
name: "Устройство |||| Устройства",
fields: {
device_id: "ID устройства",
display_name: "Название",
last_seen_ts: "Дата и время",
last_seen_ip: "IP адрес",
},
action: {
erase: {
title: "Удаление %{id}",
content: 'Действительно удалить устройство "%{name}"?',
success: "Устройство успешно удалено.",
failure: "Произошла ошибка.",
},
},
},
action: {
open: "Открыть файл в новом окне",
users_media: {
name: "Файлы",
fields: {
media_id: "ID файла",
media_length: "Размер файла (в байтах)",
media_type: "Тип",
upload_name: "Имя файла",
quarantined_by: "На карантине",
safe_from_quarantine: "Защитить от карантина",
created_ts: "Создано",
last_access_ts: "Последний доступ",
},
action: {
open: "Открыть файл в новом окне",
},
},
},
protect_media: {
action: {
create: "Не защищён, установить защиту",
delete: "Защищён, снять защиту",
none: "На карантине",
send_success: "Статус защиты успешно изменён.",
send_failure: "Произошла ошибка.",
protect_media: {
action: {
create: "Не защищён, установить защиту",
delete: "Защищён, снять защиту",
none: "На карантине",
send_success: "Статус защиты успешно изменён.",
send_failure: "Произошла ошибка.",
},
},
},
quarantine_media: {
action: {
name: "Карантин",
create: "Поместить на карантин",
delete: "На карантине, снять карантин",
none: "Защищено от карантина",
send_success: "Статус карантина успешно изменён.",
send_failure: "Произошла ошибка.",
quarantine_media: {
action: {
name: "Карантин",
create: "Поместить на карантин",
delete: "На карантине, снять карантин",
none: "Защищено от карантина",
send_success: "Статус карантина успешно изменён.",
send_failure: "Произошла ошибка.",
},
},
},
pushers: {
name: "Пушер |||| Пушеры",
fields: {
app: риложение",
app_display_name: "Название приложения",
app_id: "ID приложения",
device_display_name: "Название устройства",
kind: "Вид",
lang: "Язык",
profile_tag: "Тег профиля",
pushkey: "Ключ",
data: { url: "URL" },
pushers: {
name: "Пушер |||| Пушеры",
fields: {
app: "Приложение",
app_display_name: "Название приложения",
app_id: "ID приложения",
device_display_name: "Название устройства",
kind: "Вид",
lang: "Язык",
profile_tag: "Тег профиля",
pushkey: "Ключ",
data: { url: "URL" },
},
},
},
servernotices: {
name: "Серверные уведомления",
send: "Отправить серверные уведомления",
fields: {
body: "Сообщение",
servernotices: {
name: "Серверные уведомления",
send: "Отправить серверные уведомления",
fields: {
body: "Сообщение",
},
action: {
send: "Отправить",
send_success: "Серверное уведомление успешно отправлено.",
send_failure: "Произошла ошибка.",
},
helper: {
send: 'Отправить серверное уведомление выбранным пользователям. На сервере должна быть активна функция "Server Notices".',
},
},
action: {
send: "Отправить",
send_success: "Серверное уведомление успешно отправлено.",
send_failure: "Произошла ошибка.",
user_media_statistics: {
name: "Файлы пользователей",
fields: {
media_count: "Количество файлов",
media_length: "Размер файлов",
},
},
helper: {
send: 'Отправить серверное уведомление выбранным пользователям. На сервере должна быть активна функция "Server Notices".',
forward_extremities: {
name: "Оконечности",
fields: {
id: "ID события",
received_ts: "Дата и время",
depth: "Глубина",
state_group: "Группа состояния",
},
},
},
user_media_statistics: {
name: "Файлы пользователей",
fields: {
media_count: "Количество файлов",
media_length: "Размер файлов",
room_state: {
name: "События состояния",
fields: {
type: "Тип",
content: "Содержимое",
origin_server_ts: "Дата отправки",
sender: "Отправитель",
},
},
},
forward_extremities: {
name: "Оконечности",
fields: {
id: "ID события",
received_ts: "Дата и время",
depth: "Глубина",
state_group: "Группа состояния",
room_directory: {
name: "Каталог комнат",
fields: {
world_readable: "Гости могут просматривать без входа",
guest_can_join: "Гости могут войти",
},
action: {
title:
"Удалить комнату из каталога |||| Удалить %{smart_count} комнаты из каталога |||| Удалить %{smart_count} комнат из каталога",
content:
"Действительно удалить комнату из каталога? |||| Действительно удалить %{smart_count} комнаты из каталога? |||| Действительно удалить %{smart_count} комнат из каталога?",
erase: "Удалить из каталога комнат",
create: "Опубликовать в каталоге комнат",
send_success: "Комната успешно опубликована.",
send_failure: "Произошла ошибка.",
},
},
},
room_state: {
name: "События состояния",
fields: {
type: "Тип",
content: "Содержимое",
origin_server_ts: "Дата отправки",
sender: "Отправитель",
destinations: {
name: "Федерация",
fields: {
destination: "Назначение",
failure_ts: "Дата и время ошибки",
retry_last_ts: "Дата и время последней попытки",
retry_interval: "Интервал между попытками",
last_successful_stream_ordering: "Последний успешный поток",
stream_ordering: "Поток",
},
action: { reconnect: "Переподключиться" },
},
},
room_directory: {
name: "Каталог комнат",
fields: {
world_readable: "Гости могут просматривать без входа",
guest_can_join: "Гости могут войти",
registration_tokens: {
name: "Токены регистрации",
fields: {
token: "Токен",
valid: "Рабочий токен",
uses_allowed: "Количество использований",
pending: "Ожидает",
completed: "Завершено",
expiry_time: "Дата окончания",
length: "Длина",
},
helper: { length: "Длина токена, если токен не задан." },
},
action: {
title:
"Удалить комнату из каталога |||| Удалить %{smart_count} комнаты из каталога |||| Удалить %{smart_count} комнат из каталога",
content:
"Действительно удалить комнату из каталога? |||| Действительно удалить %{smart_count} комнаты из каталога? |||| Действительно удалить %{smart_count} комнат из каталога?",
erase: "Удалить из каталога комнат",
create: "Опубликовать в каталоге комнат",
send_success: "Комната успешно опубликована.",
send_failure: "Произошла ошибка.",
},
},
destinations: {
name: "Федерация",
fields: {
destination: "Назначение",
failure_ts: "Дата и время ошибки",
retry_last_ts: "Дата и время последней попытки",
retry_interval: "Интервал между попытками",
last_successful_stream_ordering: "Последний успешный поток",
stream_ordering: "Поток",
},
action: { reconnect: "Переподключиться" },
},
registration_tokens: {
name: "Токены регистрации",
fields: {
token: "Токен",
valid: "Рабочий токен",
uses_allowed: "Количество использований",
pending: "Ожидает",
completed: "Завершено",
expiry_time: "Дата окончания",
length: "Длина",
},
helper: { length: "Длина токена, если токен не задан." },
},
},
},
};
export default ru;

View File

@@ -2,7 +2,7 @@ import chineseMessages from "@haxqer/ra-language-chinese";
import { SynapseTranslationMessages } from ".";
const zh: SynapseTranslationMessages = {
const fixedChineseMessages = {
...chineseMessages,
ra: {
...chineseMessages.ra,
@@ -11,7 +11,27 @@ const zh: SynapseTranslationMessages = {
no_filtered_results: "没有结果",
clear_filters: "清除所有过滤器",
},
action: {
...chineseMessages.ra.action,
update_application: "Anwendung aktualisieren",
},
page: {
...chineseMessages.ra.page,
access_denied: "拒绝访问",
authentication_error: "认证错误",
},
message: {
...chineseMessages.ra.message,
access_denied:
"您没有访问此页面的权限。",
authentication_error:
"身份验证服务器返回错误,无法验证您的凭据。",
},
},
}
const zh: SynapseTranslationMessages = {
...fixedChineseMessages,
synapseadmin: {
auth: {
base_url: "服务器 URL",
@@ -21,6 +41,14 @@ const zh: SynapseTranslationMessages = {
protocol_error: "URL 需要以'http://'或'https://'作为起始",
url_error: "不是一个有效的 Matrix 服务器地址",
sso_sign_in: "使用 SSO 登录",
credentials: "凭证",
access_token: "访问令牌",
logout_acces_token_dialog: {
title: "您正在使用现有的 Matrix 访问令牌。",
content: "您想销毁此会话(可能在其他地方使用,例如在 Matrix 客户端中)还是仅从管理面板退出?",
confirm: "销毁会话",
cancel: "仅从管理面板退出",
},
},
users: {
invalid_user_id: "必须要是一个有效的 Matrix 用户 ID ,例如 @user_id:homeserver",

View File

@@ -3,14 +3,33 @@ import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import { AppContext } from "./AppContext";
import { AppContext, MenuItem } from "./AppContext";
import storage from "./storage";
fetch("config.json")
.then(res => res.json())
.then(props => {
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
storage.setItem("support_url", props.supportURL);
if (props.asManagedUsers) {
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
}
let menu: MenuItem[] = [];
if (props.menu) {
menu = props.menu;
}
if (props.supportURL) {
const migratedSupportURL = {
label: "Contact support",
icon: "SupportAgent",
url: props.supportURL,
};
console.warn("supportURL config option is deprecated. Please, use the menu option instead. Automatically migrated to the new menu option:", migratedSupportURL);
menu.push(migratedSupportURL as MenuItem);
}
if (menu.length > 0) {
storage.setItem("menu", JSON.stringify(menu));
}
return createRoot(document.getElementById("root")).render(
<React.StrictMode>
<AppContext.Provider value={props}>

View File

@@ -1,8 +1,6 @@
import { useState, useEffect } from "react";
import LockIcon from "@mui/icons-material/Lock";
import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Typography } from "@mui/material";
import { styled } from "@mui/material/styles";
import { Avatar, Box, Button, Card, CardActions, CircularProgress, MenuItem, Select, Tab, Tabs, Typography } from "@mui/material";
import {
Form,
FormDataConsumer,
@@ -17,7 +15,7 @@ import {
useLocales,
} from "react-admin";
import { useFormContext } from "react-hook-form";
import LoginFormBox from "../components/LoginFormBox";
import { useAppContext } from "../AppContext";
import {
getServerVersion,
@@ -29,66 +27,18 @@ import {
} from "../synapse/synapse";
import storage from "../storage";
const FormBox = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
minHeight: "calc(100vh - 1rem)",
alignItems: "center",
justifyContent: "flex-start",
background: "url(./images/floating-cogs.svg)",
backgroundColor: "#f9f9f9",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
[`& .card`]: {
width: "30rem",
marginTop: "6rem",
marginBottom: "6rem",
},
[`& .avatar`]: {
margin: "1rem",
display: "flex",
justifyContent: "center",
},
[`& .icon`]: {
backgroundColor: theme.palette.grey[500],
},
[`& .hint`]: {
marginTop: "1em",
marginBottom: "1em",
display: "flex",
justifyContent: "center",
color: theme.palette.grey[600],
},
[`& .form`]: {
padding: "0 1rem 1rem 1rem",
},
[`& .select`]: {
marginBottom: "2rem",
},
[`& .actions`]: {
padding: "0 1rem 1rem 1rem",
},
[`& .serverVersion`]: {
color: theme.palette.grey[500],
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
marginLeft: "0.5rem",
},
[`& .matrixVersions`]: {
color: theme.palette.grey[500],
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
fontSize: "0.8rem",
marginBottom: "1rem",
marginLeft: "0.5rem",
},
}));
export type LoginMethod = "credentials" | "accessToken";
const LoginPage = () => {
const login = useLogin();
const notify = useNotify();
const { restrictBaseUrl } = useAppContext();
const allowSingleBaseUrl = typeof restrictBaseUrl === "string";
const allowMultipleBaseUrls = Array.isArray(restrictBaseUrl);
const allowMultipleBaseUrls =
Array.isArray(restrictBaseUrl) &&
restrictBaseUrl.length > 0 &&
restrictBaseUrl[0] !== "" &&
restrictBaseUrl[0] !== null;
const allowAnyBaseUrl = !(allowSingleBaseUrl || allowMultipleBaseUrls);
const [loading, setLoading] = useState(false);
const [supportPassAuth, setSupportPassAuth] = useState(true);
@@ -98,8 +48,13 @@ const LoginPage = () => {
const base_url = allowSingleBaseUrl ? restrictBaseUrl : storage.getItem("base_url");
const [ssoBaseUrl, setSSOBaseUrl] = useState("");
const loginToken = /\?loginToken=([a-zA-Z0-9_-]+)/.exec(window.location.href);
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credentials");
useEffect(() => {
if (!loginToken) {
return;
}
if (loginToken) {
const ssoToken = loginToken[1];
console.log("SSO token is", ssoToken);
// Prevent further requests
@@ -127,7 +82,7 @@ const LoginPage = () => {
console.error(error);
});
}
}
}, [loginToken]);
const validateBaseUrl = value => {
if (!value.match(/^(http|https):\/\//)) {
@@ -213,29 +168,53 @@ const LoginPage = () => {
return (
<>
<Box>
<TextInput
autoFocus
source="username"
label="ra.auth.username"
autoComplete="username"
disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange}
resettable
validate={required()}
/>
</Box>
<Box>
<PasswordInput
source="password"
label="ra.auth.password"
type="password"
autoComplete="current-password"
disabled={loading || !supportPassAuth}
resettable
validate={required()}
/>
</Box>
<Tabs
value={loginMethod}
onChange={(_, newValue) => setLoginMethod(newValue as LoginMethod)}
indicatorColor="primary"
textColor="primary"
centered
>
<Tab label={translate("synapseadmin.auth.credentials")} value="credentials" />
<Tab label={translate("synapseadmin.auth.access_token")} value="accessToken" />
</Tabs>
{loginMethod === "credentials" ? (
<>
<Box>
<TextInput
autoFocus
source="username"
label="ra.auth.username"
autoComplete="username"
disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange}
resettable
validate={required()}
/>
</Box>
<Box>
<PasswordInput
source="password"
label="ra.auth.password"
type="password"
autoComplete="current-password"
disabled={loading || !supportPassAuth}
resettable
validate={required()}
/>
</Box>
</>
) : (
<Box>
<TextInput
source="accessToken"
label="synapseadmin.auth.access_token"
disabled={loading}
resettable
validate={required()}
/>
</Box>
)}
<Box>
<TextInput
source="base_url"
@@ -263,15 +242,13 @@ const LoginPage = () => {
return (
<Form defaultValues={{ base_url: base_url }} onSubmit={handleSubmit} mode="onTouched">
<FormBox>
<LoginFormBox>
<Card className="card">
<Box className="avatar">
{loading ? (
<CircularProgress size={25} thickness={2} />
) : (
<Avatar className="icon">
<LockIcon />
</Avatar>
<Avatar sx={{ width: "120px", height: "120px" }} src="./images/logo.webp"/>
)}
</Box>
<Box className="hint">{translate("synapseadmin.auth.welcome")}</Box>
@@ -290,7 +267,7 @@ const LoginPage = () => {
))}
</Select>
<FormDataConsumer>{formDataProps => <UserData {...formDataProps} />}</FormDataConsumer>
<CardActions className="actions">
{loginMethod === "credentials" && <CardActions className="actions">
<Button
variant="contained"
type="submit"
@@ -309,10 +286,21 @@ const LoginPage = () => {
>
{translate("synapseadmin.auth.sso_sign_in")}
</Button>
</CardActions>
</CardActions>}
{loginMethod === "accessToken" && <CardActions className="actions">
<Button
variant="contained"
type="submit"
color="primary"
disabled={loading}
fullWidth
>
{translate("ra.auth.sign_in")}
</Button>
</CardActions>}
</Box>
</Card>
</FormBox>
</LoginFormBox>
<Notification />
</Form>
);

View File

@@ -4,9 +4,11 @@ import AutorenewIcon from "@mui/icons-material/Autorenew";
import DestinationsIcon from "@mui/icons-material/CloudQueue";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import ViewListIcon from "@mui/icons-material/ViewList";
import ErrorIcon from '@mui/icons-material/Error';
import {
Button,
Datagrid,
DatagridConfigurable,
DateField,
List,
ListProps,
@@ -21,6 +23,7 @@ import {
Tab,
TabbedShowLayout,
TextField,
FunctionField,
TopToolbar,
useRecordContext,
useDelete,
@@ -35,13 +38,6 @@ import { get } from "lodash";
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
const destinationRowSx = (record: RaRecord) => ({
backgroundColor: record.retry_last_ts > 0 ? "warning.light" : "primary.contrastText",
"& .MuiButtonBase-root": {
color: "primary.dark",
},
});
const destinationFilters = [<SearchInput source="destination" alwaysOn />];
export const DestinationReconnectButton = () => {
@@ -105,7 +101,22 @@ const RetryDateField = (props: DateFieldProps) => {
return <DateField {...props} />;
};
const destinationFieldRender = (record: RaRecord) => {
if (record.retry_last_ts > 0) {
return (
<>
<ErrorIcon fontSize="inherit" color="error" sx={{verticalAlign: "middle"}}/>
{record.destination}
</>
);
}
return <> {record.destination} </>;
}
export const DestinationList = (props: ListProps) => {
const record = useRecordContext(props);
return (
<List
{...props}
@@ -113,14 +124,14 @@ export const DestinationList = (props: ListProps) => {
pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }}
>
<Datagrid rowSx={destinationRowSx} rowClick={id => `${id}/show/rooms`} bulkActionButtons={false}>
<TextField source="destination" />
<DatagridConfigurable rowClick={id => `${id}/show/rooms`} bulkActionButtons={false}>
<FunctionField source="destination" render={destinationFieldRender} />
<DateField source="failure_ts" showTime options={DATE_FORMAT} />
<RetryDateField source="retry_last_ts" showTime options={DATE_FORMAT} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
<DestinationReconnectButton />
</Datagrid>
</DatagridConfigurable>
</List>
);
};

View File

@@ -4,6 +4,7 @@ import {
Create,
CreateProps,
Datagrid,
DatagridConfigurable,
DateField,
DateTimeInput,
Edit,
@@ -39,13 +40,13 @@ export const RegistrationTokenList = (props: ListProps) => (
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<DatagridConfigurable 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>
</DatagridConfigurable>
</List>
);

View File

@@ -3,6 +3,7 @@ import ViewListIcon from "@mui/icons-material/ViewList";
import ReportIcon from "@mui/icons-material/Warning";
import {
Datagrid,
DatagridConfigurable,
DateField,
DeleteButton,
List,
@@ -90,13 +91,13 @@ const ReportShowActions = () => {
export const ReportList = (props: ListProps) => (
<List {...props} pagination={<ReportPagination />} sort={{ field: "received_ts", order: "DESC" }}>
<Datagrid rowClick="show" bulkActionButtons={false}>
<DatagridConfigurable rowClick="show" bulkActionButtons={false}>
<TextField source="id" sortable={false} />
<DateField source="received_ts" showTime options={DATE_FORMAT} sortable={true} />
<TextField sortable={false} source="user_id" />
<TextField sortable={false} source="name" />
<TextField sortable={false} source="score" />
</Datagrid>
</DatagridConfigurable>
</List>
);

View File

@@ -187,7 +187,7 @@ export const RoomShow = (props: ShowProps) => {
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
<TextField source="type" sortable={false} />
<DateField source="origin_server_ts" showTime options={DATE_FORMAT} sortable={false} />
<TextField source="content" sortable={false} />
<FunctionField source="content" sortable={false} render={record => `${JSON.stringify(record.content, null, 2)}`} />
<ReferenceField source="sender" reference="users" sortable={false}>
<TextField source="id" />
</ReferenceField>

View File

@@ -1,6 +1,7 @@
import PermMediaIcon from "@mui/icons-material/PermMedia";
import {
Datagrid,
DatagridConfigurable,
ExportButton,
List,
ListProps,
@@ -37,12 +38,12 @@ export const UserMediaStatsList = (props: ListProps) => (
pagination={<UserMediaStatsPagination />}
sort={{ field: "media_length", order: "DESC" }}
>
<Datagrid rowClick={id => "/users/" + id + "/media"} bulkActionButtons={false}>
<DatagridConfigurable rowClick={id => "/users/" + id + "/media"} bulkActionButtons={false}>
<TextField source="user_id" label="resources.users.fields.id" />
<TextField source="displayname" label="resources.users.fields.displayname" />
<NumberField source="media_count" />
<NumberField source="media_length" />
</Datagrid>
</DatagridConfigurable>
</List>
);

View File

@@ -15,6 +15,7 @@ import {
ArrayField,
Button,
Datagrid,
DatagridConfigurable,
DateField,
Create,
CreateProps,
@@ -51,8 +52,8 @@ import {
NumberField,
useListContext,
useNotify,
ToolbarClasses,
Identifier,
ToolbarClasses,
RaRecord,
ImageInput,
ImageField,
@@ -147,10 +148,6 @@ const UserBulkActionButtons = () => {
);
};
const usersRowClick = (id: Identifier, resource: string, record: RaRecord): string => {
return `/users/${id}`;
};
export const UserList = (props: ListProps) => (
<List
{...props}
@@ -160,8 +157,11 @@ export const UserList = (props: ListProps) => (
actions={<UserListActions />}
pagination={<UserPagination />}
>
<Datagrid rowClick={usersRowClick} bulkActionButtons={<UserBulkActionButtons />}>
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} />
<DatagridConfigurable
rowClick={(id: Identifier, resource: string) => `/${resource}/${id}`}
bulkActionButtons={<UserBulkActionButtons />}
>
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} sortBy="avatar_url" />
<TextField source="id" sortBy="name" />
<TextField source="displayname" />
<BooleanField source="is_guest" />
@@ -170,7 +170,7 @@ export const UserList = (props: ListProps) => (
<BooleanField source="locked" />
<BooleanField source="erased" sortable={false} />
<DateField source="creation_ts" label="resources.users.fields.creation_ts_ms" showTime options={DATE_FORMAT} />
</Datagrid>
</DatagridConfigurable>
</List>
);
@@ -212,9 +212,7 @@ const UserEditActions = () => {
export const UserCreate = (props: CreateProps) => (
<Create
{...props}
redirect={(resource, id, data) => {
return `users/${id}`;
}}
redirect={(resource: string | undefined, id: Identifier | undefined) => `${resource}/${id}`}
>
<SimpleForm>
<TextInput source="id" autoComplete="off" validate={validateUser} />

View File

@@ -23,14 +23,14 @@ describe("authProvider", () => {
})
);
const ret: undefined = await authProvider.login({
const ret = await authProvider.login({
base_url: "http://example.com",
username: "@user:example.com",
password: "secret",
});
expect(ret).toBe(undefined);
expect(fetch).toBeCalledWith("http://example.com/_matrix/client/r0/login", {
expect(ret).toEqual({redirectTo: "/"});
expect(fetch).toHaveBeenCalledWith("http://example.com/_matrix/client/v3/login", {
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","identifier":{"type":"m.id.user","user":"@user:example.com"},"password":"secret"}',
headers: new Headers({
Accept: "application/json",
@@ -55,13 +55,13 @@ describe("authProvider", () => {
})
);
const ret: undefined = await authProvider.login({
const ret = await authProvider.login({
base_url: "https://example.com/",
loginToken: "login_token",
});
expect(ret).toBe(undefined);
expect(fetch).toHaveBeenCalledWith("https://example.com/_matrix/client/r0/login", {
expect(ret).toEqual({redirectTo: "/"});
expect(fetch).toHaveBeenCalledWith("https://example.com/_matrix/client/v3/login", {
body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}',
headers: new Headers({
Accept: "application/json",
@@ -83,7 +83,7 @@ describe("authProvider", () => {
await authProvider.logout(null);
expect(fetch).toBeCalledWith("example.com/_matrix/client/r0/logout", {
expect(fetch).toHaveBeenCalledWith("example.com/_matrix/client/v3/logout", {
headers: new Headers({
Accept: "application/json",
Authorization: "Bearer foo",
@@ -123,7 +123,9 @@ describe("authProvider", () => {
describe("getPermissions", () => {
it("should do nothing", async () => {
await expect(authProvider.getPermissions(null)).resolves.toBeUndefined();
if (authProvider.getPermissions) {
await expect(authProvider.getPermissions(null)).resolves.toBeUndefined();
}
});
});
});

View File

@@ -1,7 +1,8 @@
import { AuthProvider, HttpError, Options, fetchUtils, useTranslate } from "react-admin";
import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin";
import storage from "../storage";
import { MatrixError, displayError } from "../components/error";
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
const authProvider: AuthProvider = {
// called when the user attempts to log in
@@ -10,14 +11,16 @@ const authProvider: AuthProvider = {
username,
password,
loginToken,
accessToken,
}: {
base_url: string;
username: string;
password: string;
loginToken: string;
accessToken: string;
}) => {
console.log("login ");
const options: Options = {
let options: Options = {
method: "POST",
body: JSON.stringify(
Object.assign(
@@ -55,11 +58,30 @@ const authProvider: AuthProvider = {
storage.setItem("base_url", base_url);
const decoded_base_url = window.decodeURIComponent(base_url);
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login");
let response;
try {
if (accessToken) {
// this a login with an already obtained access token, let's just validate it
options = {
headers: new Headers({
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
}),
};
}
response = await fetchUtils.fetchJson(login_api_url, options);
const json = response.json;
storage.setItem("home_server", accessToken ? json.user_id.split(":")[1] : json.home_server);
storage.setItem("user_id", json.user_id);
storage.setItem("access_token", accessToken ? accessToken : json.access_token);
storage.setItem("device_id", json.device_id);
storage.setItem("login_type", accessToken ? "accessToken" : "credentials");
return Promise.resolve({redirectTo: "/"});
} catch(err) {
const error = err as HttpError;
const errorStatus = error.status;
@@ -71,20 +93,51 @@ const authProvider: AuthProvider = {
errMsg,
errorStatus,
)
);
);
}
},
getIdentity: async () => {
const access_token = storage.getItem("access_token");
const user_id = storage.getItem("user_id");
const base_url = storage.getItem("base_url");
if (typeof access_token !== "string" || typeof user_id !== "string" || typeof base_url !== "string") {
return Promise.reject();
}
const json = response.json;
storage.setItem("home_server", json.home_server);
storage.setItem("user_id", json.user_id);
storage.setItem("access_token", json.access_token);
storage.setItem("device_id", json.device_id);
const options: Options = {
headers: new Headers({
Accept: "application/json",
Authorization: `Bearer ${access_token}`,
}),
};
const whoami_api_url = base_url + `/_matrix/client/v3/profile/${user_id}`;
try {
let avatar_url = "";
const response = await fetchUtils.fetchJson(whoami_api_url, options);
if (response.json.avatar_url) {
const mediaresp = await fetchAuthenticatedMedia(response.json.avatar_url, "thumbnail");
const blob = await mediaresp.blob();
avatar_url = URL.createObjectURL(blob);
}
return Promise.resolve({
id: user_id,
fullName: response.json.displayname,
avatar: avatar_url,
});
} catch (err) {
console.log("Error getting identity", err);
return Promise.reject();
}
},
// called when the user clicks on the logout button
logout: async () => {
console.log("logout");
const logout_api_url = storage.getItem("base_url") + "/_matrix/client/r0/logout";
const logout_api_url = storage.getItem("base_url") + "/_matrix/client/v3/logout";
const access_token = storage.getItem("access_token");
const options: Options = {
@@ -102,6 +155,7 @@ const authProvider: AuthProvider = {
console.log("Error logging out", err);
} finally {
storage.removeItem("access_token");
storage.removeItem("login_type");
}
}
},

View File

@@ -575,13 +575,28 @@ const baseDataProvider: SynapseDataProvider = {
getMany: async (resource, params) => {
console.log("getMany " + resource);
const homeserver = storage.getItem("base_url");
if (!homeserver || !(resource in resourceMap)) throw Error("Homerserver not set");
const base_url = storage.getItem("base_url");
const homeserver = storage.getItem("home_server");
if (!base_url || !(resource in resourceMap)) throw Error("base_url not set");
const res = resourceMap[resource];
const endpoint_url = homeserver + res.path;
const responses = await Promise.all(params.ids.map(id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)));
const endpoint_url = base_url + res.path;
const responses = await Promise.all(params.ids.map(id => {
// edge case: when user is external / federated, homeserver will return error, as querying external users via
// /_synapse/admin/v2/users is not allowed.
// That leads to an issue when a user is referenced (e.g., in room state datagrid) - the user cell is just empty.
// To avoid that, we fake the response with one specific field (name) which is used in the datagrid.
if (homeserver && resource === "users") {
if (!(<string>id).endsWith(homeserver)) {
const json = {
name: id,
};
return Promise.resolve({ json });
}
}
return jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`);
}));
return {
data: responses.map(({ json }) => res.map(json)),
total: responses.length,

View File

@@ -8,10 +8,35 @@ export default defineConfig({
plugins: [
react(),
vitePluginVersionMark({
name: "Synapse Admin",
command: "git describe --tags || git rev-parse --short HEAD",
ifMeta: true,
ifLog: true,
ifMeta: false,
ifLog: false,
ifGlobal: true,
outputFile: (version) => ({
path: "manifest.json",
content: JSON.stringify({
name: "Synapse Admin",
version: version,
description: "Synapse Admin is an admin console for synapse Matrix homeserver with additional features.",
categories: ["productivity", "utilities"],
orientation: "landscape",
icons: [{
src: "favicon.ico",
sizes: "32x32",
type: "image/x-icon"
},{
src: "images/logo.webp",
sizes: "512x512",
type: "image/webp",
purpose: "any maskable"
}],
start_url: ".",
display: "standalone",
theme_color: "#000000",
background_color: "#ffffff"
}),
}),
}),
],
});

1230
yarn.lock

File diff suppressed because it is too large Load Diff