├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── dependabot.yml └── workflows │ └── workflow.yml ├── .gitignore ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── .watchmanconfig ├── .yarn ├── releases │ └── yarn-4.4.1.cjs └── sdks │ ├── eslint │ ├── bin │ │ └── eslint.js │ ├── lib │ │ ├── api.js │ │ └── unsupported-api.js │ └── package.json │ ├── integrations.yml │ ├── prettier │ ├── bin │ │ └── prettier.cjs │ ├── index.cjs │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ ├── tsserverlibrary.js │ └── typescript.js │ └── package.json ├── Dockerfile ├── Dockerfile.build ├── LICENSE ├── README.md ├── docker-compose-dev.yml ├── docker-compose.yml ├── docs ├── README.md ├── config.md ├── cors-credentials.md ├── custom-menu.md ├── prefill-login-form.md ├── restrict-hs.md ├── reverse-proxy.md ├── system-users.md └── user-badges.md ├── index.html ├── jest.config.ts ├── justfile ├── package.json ├── public ├── config.json ├── data │ └── example.csv ├── favicon.ico ├── images │ ├── floating-cogs.svg │ └── logo.webp └── robots.txt ├── screenshots ├── auth.webp ├── etke.cc │ ├── server-actions │ │ └── page.webp │ ├── server-commands │ │ └── panel.webp │ ├── server-notifications │ │ ├── badge.webp │ │ └── page.webp │ └── server-status │ │ ├── indicator-sidebar.webp │ │ ├── indicator.webp │ │ └── page.webp └── screenshots.jpg ├── src ├── App.test.tsx ├── App.tsx ├── Context.tsx ├── components │ ├── AdminLayout.tsx │ ├── AvatarField.test.tsx │ ├── AvatarField.tsx │ ├── DeleteRoomButton.tsx │ ├── DeleteUserButton.tsx │ ├── DeviceRemoveButton.tsx │ ├── ExperimentalFeatures.tsx │ ├── Footer.tsx │ ├── LoginFormBox.tsx │ ├── ServerNotices.tsx │ ├── UserAccountData.tsx │ ├── UserRateLimits.tsx │ ├── etke.cc │ │ ├── CurrentlyRunningCommand.tsx │ │ ├── README.md │ │ ├── ServerActionsPage.tsx │ │ ├── ServerCommandsPanel.tsx │ │ ├── ServerNotificationsBadge.tsx │ │ ├── ServerNotificationsPage.tsx │ │ ├── ServerStatusBadge.tsx │ │ ├── ServerStatusPage.tsx │ │ ├── hooks │ │ │ └── useServerCommands.ts │ │ └── schedules │ │ │ ├── components │ │ │ ├── ScheduledCommandCreate.tsx │ │ │ ├── ScheduledCommandEdit.tsx │ │ │ ├── recurring │ │ │ │ ├── RecurringCommandEdit.tsx │ │ │ │ ├── RecurringCommandsList.tsx │ │ │ │ └── RecurringDeleteButton.tsx │ │ │ └── scheduled │ │ │ │ ├── ScheduledCommandEdit.tsx │ │ │ │ ├── ScheduledCommandShow.tsx │ │ │ │ ├── ScheduledCommandsList.tsx │ │ │ │ └── ScheduledDeleteButton.tsx │ │ │ └── hooks │ │ │ ├── useRecurringCommands.tsx │ │ │ └── useScheduledCommands.tsx │ ├── media.tsx │ └── user-import │ │ ├── ConflictModeCard.tsx │ │ ├── ErrorsCard.tsx │ │ ├── ResultsCard.tsx │ │ ├── StartImportCard.tsx │ │ ├── StatsCard.tsx │ │ ├── UploadCard.tsx │ │ ├── UserImport.tsx │ │ ├── types.ts │ │ └── useImportFile.tsx ├── i18n │ ├── de.ts │ ├── en.ts │ ├── fa.ts │ ├── fr.ts │ ├── index.d.ts │ ├── it.ts │ ├── ru.ts │ └── zh.ts ├── index.tsx ├── jest.setup.ts ├── pages │ ├── LoginPage.test.tsx │ └── LoginPage.tsx ├── resources │ ├── destinations.tsx │ ├── registration_tokens.tsx │ ├── reports.tsx │ ├── room_directory.tsx │ ├── rooms.tsx │ ├── user_media_statistics.tsx │ └── users.tsx ├── synapse │ ├── authProvider.test.ts │ ├── authProvider.ts │ ├── dataProvider.test.ts │ ├── dataProvider.ts │ ├── matrix.test.ts │ └── matrix.ts └── utils │ ├── config.ts │ ├── date.ts │ ├── decodeURLComponent.ts │ ├── error.ts │ ├── fetchMedia.ts │ ├── icons.ts │ ├── mxid.ts │ └── password.ts ├── testdata ├── element │ ├── config.json │ └── nginx.conf └── synapse │ ├── homeserver.yaml │ ├── synapse.log.config │ └── synapse.signing.key ├── tsconfig.eslint.json ├── tsconfig.json ├── tsconfig.vite.json ├── vite.config.ts └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | /testdata 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | max_line_length = 120 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | yarn*.cjs binary 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Table of Contents: 4 | 5 | 6 | 7 | * [Did you find a bug?](#did-you-find-a-bug) 8 | * [Is it a Security Vulnerability?](#is-it-a-security-vulnerability) 9 | * [Is it already a known issue?](#is-it-already-a-known-issue) 10 | * [Reporting a Bug](#reporting-a-bug) 11 | * [Is there a patch for the bug?](#is-there-a-patch-for-the-bug) 12 | * [Do you want to add a new feature?](#do-you-want-to-add-a-new-feature) 13 | * [Is it just an idea?](#is-it-just-an-idea) 14 | * [Is there a patch for the feature?](#is-there-a-patch-for-the-feature) 15 | * [Do you have questions about the Synapse Admin project or need guidance?](#do-you-have-questions-about-the-synapse-admin-project-or-need-guidance) 16 | 17 | 18 | 19 | ## Did you find a bug? 20 | 21 | ### Is it a Security Vulnerability? 22 | 23 | Please follow the [Security Policy](https://github.com/etkecc/synapse-admin/blob/main/.github/SECURITY.md) for reporting 24 | security vulnerabilities. 25 | 26 | ### Is it already a known issue? 27 | 28 | Please ensure the bug was not already reported by searching [the Issues section](https://github.com/etkecc/synapse-admin/issues). 29 | 30 | ### Reporting a Bug 31 | 32 | If you think you have found a bug in Synapse Admin, it is not a security vulnerability, and it is not already reported, 33 | please open [a new issue](https://github.com/etkecc/synapse-admin/issues/new) with: 34 | * A proper title and clear description of the problem. 35 | * As much relevant information as possible: 36 | * The version of Synapse Admin you are using. 37 | * The version of Synapse you are using. 38 | * Any relevant browser console logs, failed requests details, and error messages. 39 | 40 | ### Is there a patch for the bug? 41 | 42 | If you already have a patch for the bug, please open a pull request with the patch, 43 | and mention the issue number in the pull request description. 44 | 45 | ## Do you want to add a new feature? 46 | 47 | ### Is it just an idea? 48 | 49 | Please open [a new issue](https://github.com/etkecc/synapse-admin/issues/new) with: 50 | * A proper title and clear description of the requested feature. 51 | * Any relevant information about the feature: 52 | * Why do you think this feature is needed? 53 | * How do you think it should work? (provide Synapse Admin API endpoint) 54 | * Any relevant screenshots or mockups. 55 | 56 | ### Is there a patch for the feature? 57 | 58 | If you already have a patch for the feature, please open a pull request with the patch, 59 | and mention the issue number in the pull request description. 60 | 61 | ## Do you have questions about the Synapse Admin project or need guidance? 62 | 63 | Please use the official community Matrix room: [#synapse-admin:etke.cc](https://matrix.to/#/#synapse-admin:etke.cc) 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: etkecc 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a Synapse Admin bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Browser console logs** 27 | If applicable, add the browser console's log 28 | 29 | **Instance configuration:** 30 | - Synapse Admin version: [e.g. v0.10.3-etke39] 31 | - Synapse version [v1.127.1] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Synapse Admin 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Provide related Synapse Admin API endpoints** 20 | If applicable, provide links to the Synapse Admin API's endpoints that could be used to implement that feature 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only [the last published version](https://github.com/etkecc/synapse-admin/releases/latest) of the project is supported. 6 | This means that only the latest version will receive security updates. 7 | If you are using an older version, you are strongly encouraged to upgrade to the latest version. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Please contact us using the [#synapse-admin:etke.cc](https://matrix.to/#/#synapse-admin:etke.cc) Matrix room. 12 | The Synapse Admin project is a static JS UI for the Synapse server, 13 | so it is unlikely that there are (or will be) any impactful security vulnerabilities in the project itself. 14 | However, we do not rule out the possibility of such cases, so we will be happy to receive any reports! 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 30 8 | 9 | - package-ecosystem: "docker" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | open-pull-requests-limit: 30 14 | 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | open-pull-requests-limit: 30 20 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ "main" ] 5 | tags: [ "v*" ] 6 | env: 7 | bunny_version: v0.1.0 8 | base_path: ./ 9 | permissions: 10 | checks: write 11 | contents: write 12 | packages: write 13 | pull-requests: read 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: lts/* 25 | cache: yarn 26 | - name: Install dependencies 27 | run: yarn install --immutable --network-timeout=300000 28 | - name: Build 29 | run: yarn build --base=${{ env.base_path }} 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | path: dist/ 33 | name: dist 34 | if-no-files-found: error 35 | retention-days: 1 36 | compression-level: 0 37 | overwrite: true 38 | include-hidden-files: true 39 | 40 | docker: 41 | name: Docker 42 | needs: build 43 | runs-on: self-hosted 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: actions/download-artifact@v4 47 | with: 48 | name: dist 49 | path: dist/ 50 | - name: Set up Docker Buildx 51 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 52 | - name: Login to ghcr.io 53 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 54 | with: 55 | registry: ghcr.io 56 | username: ${{ github.actor }} 57 | password: ${{ secrets.GITHUB_TOKEN }} 58 | - name: Login to hub.docker.com 59 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 60 | with: 61 | username: etkecc 62 | password: ${{ secrets.DOCKERHUB_TOKEN }} 63 | - name: Extract metadata (tags, labels) for Docker 64 | id: meta 65 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 66 | with: 67 | images: | 68 | ${{ github.repository }} 69 | ghcr.io/${{ github.repository }} 70 | registry.etke.cc/${{ github.repository }} 71 | tags: | 72 | type=raw,value=latest,enable=${{ github.ref_name == 'main' }} 73 | type=semver,pattern={{raw}} 74 | - name: Build and push 75 | uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 76 | with: 77 | platforms: linux/amd64,linux/arm64 78 | context: . 79 | push: true 80 | tags: ${{ steps.meta.outputs.tags }} 81 | labels: ${{ steps.meta.outputs.labels }} 82 | 83 | cdn: 84 | name: CDN 85 | needs: build 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v4 89 | - uses: actions/download-artifact@v4 90 | with: 91 | name: dist 92 | path: dist/ 93 | - name: Upload 94 | run: | 95 | wget -O bunny-upload.tar.gz https://github.com/etkecc/bunny-upload/releases/download/${{ env.bunny_version }}/bunny-upload_Linux_x86_64.tar.gz 96 | tar -xzf bunny-upload.tar.gz 97 | echo "${{ secrets.BUNNY_CONFIG }}" > bunny-config.yaml 98 | ./bunny-upload -c bunny-config.yaml 99 | 100 | github-release: 101 | name: Github Release 102 | needs: build 103 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 104 | runs-on: ubuntu-latest 105 | steps: 106 | - uses: actions/checkout@v4 107 | - uses: actions/download-artifact@v4 108 | with: 109 | name: dist 110 | path: dist/ 111 | - name: Prepare release 112 | run: | 113 | mv dist synapse-admin 114 | tar chvzf synapse-admin.tar.gz synapse-admin 115 | - uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 116 | with: 117 | files: synapse-admin.tar.gz 118 | generate_release_notes: true 119 | make_latest: "true" 120 | draft: false 121 | prerelease: false 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn,react,visualstudiocode 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | .pnpm-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variable files 80 | .env 81 | .env.development.local 82 | .env.test.local 83 | .env.production.local 84 | .env.local 85 | 86 | # parcel-bundler cache (https://parceljs.org/) 87 | .cache 88 | .parcel-cache 89 | 90 | # Next.js build output 91 | .next 92 | out 93 | 94 | # Nuxt.js build / generate output 95 | .nuxt 96 | dist 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and not Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # vuepress v2.x temp and cache directory 108 | .temp 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | ### Node Patch ### 136 | # Serverless Webpack directories 137 | .webpack/ 138 | 139 | # Optional stylelint cache 140 | 141 | # SvelteKit build / generate output 142 | .svelte-kit 143 | 144 | ### react ### 145 | .DS_* 146 | **/*.backup.* 147 | **/*.back.* 148 | 149 | node_modules 150 | 151 | *.sublime* 152 | 153 | psd 154 | thumb 155 | sketch 156 | 157 | ### VisualStudioCode ### 158 | .vscode/* 159 | !.vscode/settings.json 160 | !.vscode/tasks.json 161 | !.vscode/launch.json 162 | !.vscode/extensions.json 163 | !.vscode/*.code-snippets 164 | 165 | # Local History for Visual Studio Code 166 | .history/ 167 | 168 | # Built Visual Studio Code Extensions 169 | *.vsix 170 | 171 | ### VisualStudioCode Patch ### 172 | # Ignore all local history of files 173 | .history 174 | .ionide 175 | 176 | ### yarn ### 177 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 178 | 179 | .yarn/* 180 | !.yarn/releases 181 | !.yarn/patches 182 | !.yarn/plugins 183 | !.yarn/sdks 184 | !.yarn/versions 185 | 186 | # if you are NOT using Zero-installs, then: 187 | # comment the following lines 188 | !.yarn/cache 189 | 190 | # and uncomment the following lines 191 | # .pnp.* 192 | 193 | # End of https://www.toptal.com/developers/gitignore/api/node,yarn,react,visualstudiocode 194 | 195 | /testdata/synapse.data 196 | /testdata/postgres.data 197 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .yarn 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.yarn": true, 4 | "**/.pnp.*": true 5 | }, 6 | "eslint.nodePath": ".yarn/sdks", 7 | "prettier.prettierPath": ".yarn/sdks/prettier/index.cjs", 8 | "typescript.tsdk": "node_modules/typescript/lib", 9 | "typescript.enablePromptUseWorkspaceTsdk": true 10 | } 11 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": [ 3 | "testdata" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint your application uses 20 | module.exports = absRequire(`eslint`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/unsupported-api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/use-at-your-own-risk 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/use-at-your-own-risk your application uses 20 | module.exports = absRequire(`eslint/use-at-your-own-risk`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "8.57.0-sdk", 4 | "main": "./lib/api.js", 5 | "type": "commonjs", 6 | "bin": { 7 | "eslint": "./bin/eslint.js" 8 | }, 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": "./lib/api.js", 12 | "./use-at-your-own-risk": "./lib/unsupported-api.js" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/bin/prettier.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/bin/prettier.cjs 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/bin/prettier.cjs your application uses 20 | module.exports = absRequire(`prettier/bin/prettier.cjs`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier your application uses 20 | module.exports = absRequire(`prettier`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "3.2.5-sdk", 4 | "main": "./index.cjs", 5 | "type": "commonjs", 6 | "bin": "./bin/prettier.cjs" 7 | } 8 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = createRequire(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript your application uses 20 | module.exports = absRequire(`typescript`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "5.4.5-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs", 6 | "bin": { 7 | "tsc": "./bin/tsc", 8 | "tsserver": "./bin/tsserver" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/static-web-server/static-web-server:2 2 | 3 | ENV SERVER_ROOT=/app 4 | 5 | COPY ./dist /app 6 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM node:lts AS builder 2 | ARG BASE_PATH=./ 3 | WORKDIR /src 4 | COPY . /src 5 | RUN yarn config set enableTelemetry 0 && \ 6 | yarn install --immutable --network-timeout=300000 && \ 7 | yarn build --base=$BASE_PATH 8 | 9 | FROM ghcr.io/static-web-server/static-web-server:2 10 | ENV SERVER_ROOT=/app 11 | COPY --from=builder /src/dist /app 12 | -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | synapse: 3 | image: ghcr.io/element-hq/synapse:develop 4 | entrypoint: python 5 | command: "-m synapse.app.homeserver -c /config/homeserver.yaml" 6 | ports: 7 | - "8008:8008" 8 | volumes: 9 | - ./testdata/synapse:/config 10 | - ./testdata/synapse.data:/media-store 11 | 12 | postgres: 13 | image: postgres:alpine 14 | volumes: 15 | - ./testdata/postgres.data:/var/lib/postgresql/data 16 | environment: 17 | POSTGRES_USER: synapse 18 | POSTGRES_PASSWORD: synapse 19 | POSTGRES_DB: synapse 20 | POSTGRES_INITDB_ARGS: "--lc-collate C --lc-ctype C --encoding UTF8" 21 | 22 | element: 23 | image: docker.io/vectorim/element-web:latest 24 | depends_on: 25 | synapse: 26 | condition: service_healthy 27 | restart: true 28 | ports: 29 | - "8080:8080" 30 | volumes: 31 | - ./testdata/element/nginx.conf:/etc/nginx/nginx.conf:ro 32 | - /dev/null:/etc/nginx/conf.d/default.conf:ro 33 | - ./testdata/element/config.json:/app/config.json:ro 34 | 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | synapse-admin: 3 | container_name: synapse-admin 4 | hostname: synapse-admin 5 | image: ghcr.io/etkecc/synapse-admin:latest 6 | # build: 7 | # context: . 8 | # dockerfile: Dockerfile.build 9 | 10 | # to use the docker-compose as standalone without a local repo clone, 11 | # replace the context definition with this: 12 | # context: https://github.com/etkecc/synapse-admin.git 13 | 14 | # args: 15 | # - BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 16 | # if you're building on an architecture other than amd64, make sure 17 | # to define a maximum ram for node. otherwise the build will fail. 18 | # - NODE_OPTIONS="--max_old_space_size=1024" 19 | # - BASE_PATH="/synapse-admin" 20 | ports: 21 | - "8080:80" 22 | restart: unless-stopped 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Synapse Admin documentation is under construction right now, so PRs are greatly appreciated! 4 | 5 | Table of contents: 6 | 7 | 8 | * [Configuration](#configuration) 9 | * [Features](#features) 10 | * [Deployment](#deployment) 11 | 12 | 13 | 14 | ## Configuration 15 | 16 | [Full configuration documentation](./config.md) 17 | 18 | Specific configuration options: 19 | 20 | * [Customizing CORS credentials](./cors-credentials.md) 21 | * [Restricting available homeserver](./restrict-hs.md) 22 | * [System / Appservice-managed Users](./system-users.md) 23 | * [Custom Menu Items](./custom-menu.md) 24 | 25 | ## Features 26 | 27 | * [User Badges](./user-badges.md) 28 | * [Prefilling the Login Form](./prefill-login-form.md) 29 | 30 | for [etke.cc](https://etke.cc) customers only: 31 | 32 | > **Note:** The following features are only available for etke.cc customers. Due to specifics of the implementation, 33 | they are not available for any other Synapse Admin deployments. 34 | 35 | * [Server Status icon](../src/components/etke.cc/README.md#server-status-icon) 36 | * [Server Status page](../src/components/etke.cc/README.md#server-status-page) 37 | * [Server Actions page](../src/components/etke.cc/README.md#server-actions-page) 38 | * [Server Commands Panel](../src/components/etke.cc/README.md#server-commands-panel) 39 | * [Server Notifications icon](../src/components/etke.cc/README.md#server-notifications-icon) 40 | * [Server Notifications page](../src/components/etke.cc/README.md#server-notifications-page) 41 | 42 | ## Deployment 43 | 44 | * [Serving Synapse Admin behind a reverse proxy](./reverse-proxy.md) 45 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Synapse Admin could be configured using the following ways (both are optional, and both could be used together): 4 | 5 | * By providing the `config.json` file alongside with the Synapse Admin deployment, example: [admin.etke.cc/config.json](https://admin.etke.cc/config.json) 6 | * By providing configuration under the `cc.etke.synapse-admin` key in the `/.well-known/matrix/client` file, example: 7 | [demo.etke.host/.well-known/matrix/client](https://demo.etke.host/.well-known/matrix/client) 8 | 9 | In case you are an [etke.cc](https://etke.cc) customer, 10 | or use [spantaleev/matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy), 11 | or [etkecc/ansible](https://github.com/etkecc/ansible), 12 | configuration will be automatically added to the `/.well-known/matrix/client` file. 13 | 14 | **Why `/.well-known/matrix/client`?** 15 | 16 | Because any instance of Synapse Admin will automatically pick up the configuration from the homeserver. 17 | Common use case when you have a Synapse server running, but don't want (or can't) deploy Synapse Admin alongside with it. 18 | In this case, you could provide the configuration in the `/.well-known/matrix/client` file, 19 | and any Synapse Admin instance (e.g., [admin.etke.cc](https://admin.etke.cc) will pick it up. 20 | 21 | Another common case is when you have multiple Synapse servers running and want to use a single Synapse Admin instance to manage them all. 22 | In this case, you could provide the configuration in the `/.well-known/matrix/client` file for each of the servers. 23 | 24 | ## Configuration options 25 | 26 | * `restrictBaseUrl` - restrictBaseUrl restricts the Synapse Admin instance to work only with specific homeserver(-s). 27 | It accepts both a string and an array of strings. 28 | The homeserver URL should be the _actual_ homeserver URL, and not the delegated one. 29 | Example: `https://matrix.example.com` or `https://synapse.example.net` 30 | [More details](restrict-hs.md) 31 | * `corsCredentials` - configure the CORS credentials for the Synapse Admin instance. 32 | It accepts the following values: 33 | * `same-origin` (default): Cookies will be sent only if the request is made from the same origin as the server. 34 | * `include`: Cookies will be sent regardless of the origin of the request. 35 | * `omit`: Cookies will not be sent with the request. 36 | [More details](cors-credentials.md) 37 | * `asManagedUsers` - protect system user accounts managed by appservices (such as bridges) / system (such as bots) from accidental changes. 38 | By defining a list of MXID regex patterns, you can protect these accounts from accidental changes. 39 | Example: `^@baibot:example\\.com$`, `^@slackbot:example\\.com$`, `^@slack_[a-zA-Z0-9\\-]+:example\\.com$`, `^@telegrambot:example\\.com$`, `^@telegram_[a-zA-Z0-9]+:example\\.com$` 40 | [More details](system-users.md) 41 | * `menu` - add custom menu items to the main menu (sidebar) by providing a `menu` array in the config. 42 | Each `menu` item can contain the following fields: 43 | * `label` (required): The text to display in the menu. 44 | * `icon` (optional): The icon to display next to the label, one of the [src/utils/icons.ts](../src/utils/icons.ts) icons, otherwise a default icon will be used. 45 | * `url` (required): The URL to navigate to when the menu item is clicked. 46 | [More details](custom-menu.md) 47 | 48 | ## Examples 49 | 50 | ### config.json 51 | 52 | ```json 53 | { 54 | "restrictBaseUrl": [ 55 | "https://matrix.example.com", 56 | "https://synapse.example.net" 57 | ], 58 | "asManagedUsers": [ 59 | "^@baibot:example\\.com$", 60 | "^@slackbot:example\\.com$", 61 | "^@slack_[a-zA-Z0-9\\-]+:example\\.com$", 62 | "^@telegrambot:example\\.com$", 63 | "^@telegram_[a-zA-Z0-9]+:example\\.com$" 64 | ], 65 | "menu": [ 66 | { 67 | "label": "Contact support", 68 | "icon": "SupportAgent", 69 | "url": "https://github.com/etkecc/synapse-admin/issues" 70 | } 71 | ] 72 | } 73 | ``` 74 | 75 | ### `/.well-known/matrix/client` 76 | 77 | ```json 78 | { 79 | "cc.etke.synapse-admin": { 80 | "restrictBaseUrl": [ 81 | "https://matrix.example.com", 82 | "https://synapse.example.net" 83 | ], 84 | "asManagedUsers": [ 85 | "^@baibot:example\\.com$", 86 | "^@slackbot:example\\.com$", 87 | "^@slack_[a-zA-Z0-9\\-]+:example\\.com$", 88 | "^@telegrambot:example\\.com$", 89 | "^@telegram_[a-zA-Z0-9]+:example\\.com$" 90 | ], 91 | "menu": [ 92 | { 93 | "label": "Contact support", 94 | "icon": "SupportAgent", 95 | "url": "https://github.com/etkecc/synapse-admin/issues" 96 | } 97 | ] 98 | } 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/cors-credentials.md: -------------------------------------------------------------------------------- 1 | # CORS Credentials 2 | 3 | If you'd like to use cookie-based authentication 4 | (for example, [ForwardAuth with Authelia](https://github.com/Awesome-Technologies/synapse-admin/issues/655)), 5 | you can configure the `corsCredentials` option in the `config.json` file or in the `/.well-known/matrix/client` file. 6 | 7 | ## Configuration 8 | 9 | > [Documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#including_credentials) 10 | 11 | The `corsCredentials` option accepts the following values: 12 | 13 | * `same-origin` (default): Cookies will be sent only if the request is made from the same origin as the server. 14 | * `include`: Cookies will be sent regardless of the origin of the request. 15 | * `omit`: Cookies will not be sent with the request. 16 | 17 | [Configuration options](config.md) 18 | 19 | ### config.json 20 | 21 | ```json 22 | { 23 | "corsCredentials": "include" 24 | } 25 | ``` 26 | 27 | ### `/.well-known/matrix/client` 28 | 29 | ```json 30 | { 31 | "cc.etke.synapse-admin": { 32 | "corsCredentials": "include" 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/custom-menu.md: -------------------------------------------------------------------------------- 1 | # Custom Menu Items 2 | 3 | You can add custom menu items to the main menu (sidebar) by providing a `menu` array in the config. 4 | This is useful for adding links to external sites or other pages in your documentation, like a support page or internal wiki. 5 | 6 | ## Configuration 7 | 8 | The examples below contain the configuration settings to add a link to the [Synapse Admin issues](https://github.com/etke.cc/synapse-admin/issues). 9 | 10 | Each `menu` item can contain the following fields: 11 | 12 | * `label` (required): The text to display in the menu. 13 | * `icon` (optional): The icon to display next to the label, one of the [src/utils/icons.ts](../src/utils/icons.ts) icons, otherwise a 14 | default icon will be used. 15 | * `url` (required): The URL to navigate to when the menu item is clicked. 16 | 17 | [Configuration options](config.md) 18 | 19 | ### config.json 20 | 21 | ```json 22 | { 23 | "menu": [ 24 | { 25 | "label": "Contact support", 26 | "icon": "SupportAgent", 27 | "url": "https://github.com/etkecc/synapse-admin/issues" 28 | } 29 | ] 30 | } 31 | ``` 32 | 33 | ### `/.well-known/matrix/client` 34 | 35 | ```json 36 | { 37 | "cc.etke.synapse-admin": { 38 | "menu": [ 39 | { 40 | "label": "Contact support", 41 | "icon": "SupportAgent", 42 | "url": "https://github.com/etkecc/synapse-admin/issues" 43 | } 44 | ] 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/prefill-login-form.md: -------------------------------------------------------------------------------- 1 | # Prefilling the Login Form 2 | 3 | In some cases you may wish to prefill/preset the login form fields when sharing a link to a Synapse Admin instance. 4 | This can be done by adding the following query parameters to the URL: 5 | 6 | * `username` - The username to prefill in the username field. 7 | * `server` - The server to prefill in the homeserver url field. 8 | 9 | The following query params will work only if the Synapse Admin is loaded from `localhost` or `127.0.0.1`: 10 | 11 | * `password` - The password to prefill in the password field (credentials auth). **NEVER** use this in production. 12 | * `accessToken` - The access token to prefill in the access token field (access token auth). **NEVER** use this in production. 13 | 14 | > **WARNING**: Never use the `password` or `accessToken` query parameters in production as they can be easily extracted 15 | from the URL. These are only meant for development purposes and local environments. 16 | 17 | 18 | ## Examples 19 | 20 | ### Production 21 | 22 | ```bash 23 | https://admin.etke.cc?username=admin&server=https://matrix.example.com 24 | ``` 25 | 26 | This will open `Credentials` (username/password) login form with the username field prefilled with `admin` and the 27 | Homeserver URL field prefilled with `https://matrix.example.com`. 28 | 29 | ### Development and Local environments 30 | 31 | **With Password** 32 | 33 | ```bash 34 | http://localhost:8080?username=admin&server=https://matrix.example.com&password=secret 35 | ``` 36 | 37 | This will open `Credentials` (username/password) login form with the username field prefilled with `admin`, the 38 | Homeserver URL field prefilled with `https://matrix.example.com` and the password field prefilled with `secret`. 39 | 40 | 41 | **With Access Token** 42 | 43 | ```bash 44 | http://localhost:8080?server=https://matrix.example.com&accessToken=secret 45 | ``` 46 | 47 | This will open `Access Token` login form with the Homeserver URL field prefilled with `https://matrix.example.com` and 48 | the access token field prefilled with `secret`. 49 | -------------------------------------------------------------------------------- /docs/restrict-hs.md: -------------------------------------------------------------------------------- 1 | # Restricting available homeserver 2 | 3 | If you want to have your Synapse Admin instance work only with specific homeserver(-s), 4 | you can do that by setting `restrictBaseUrl` in the configuration. 5 | 6 | ## Configuration 7 | 8 | You can do that for a single homeserver or multiple homeservers at once, as `restrictBaseUrl` accepts both a string and 9 | an array of strings. 10 | 11 | The examples below contain the configuration settings to restrict the Synapse Admin instance to work only with 12 | `example.com` (with Synapse runing at `matrix.example.com`) and 13 | `example.net` (with Synapse running at `synapse.example.net`) homeservers. 14 | Note that the homeserver URL should be the _actual_ homeserver URL, and not the delegated one. 15 | 16 | So, if you have a homeserver `example.com` where users have MXIDs like `@user:example.com`, 17 | but actual Synapse is installed on `matrix.example.com` subdomain, you should use `https://matrix.example.com` in the 18 | configuration. 19 | 20 | [Configuration options](config.md) 21 | 22 | ### config.json 23 | 24 | ```json 25 | { 26 | "restrictBaseUrl": [ 27 | "https://matrix.example.com", 28 | "https://synapse.example.net" 29 | ] 30 | } 31 | ``` 32 | 33 | ### `/.well-known/matrix/client` 34 | 35 | ```json 36 | { 37 | "cc.etke.synapse-admin": { 38 | "restrictBaseUrl": [ 39 | "https://matrix.example.com", 40 | "https://synapse.example.net" 41 | ] 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/reverse-proxy.md: -------------------------------------------------------------------------------- 1 | # Serving Synapse Admin behind a reverse proxy 2 | 3 | Your are supposed to do so for any service you want to expose to the internet, 4 | and here you can find specific instructions and example configurations for Synapse Admin. 5 | 6 | ## Nginx 7 | 8 | Place the config below into `/etc/nginx/conf.d/synapse-admin.conf` (don't forget to replace `server_name` and `root`): 9 | 10 | ```nginx 11 | server { 12 | listen 80; 13 | listen [::]:80; 14 | server_name example.com; # REPLACE with your domain 15 | root /var/www/synapse-admin; # REPLACE with path where you extracted synapse admin 16 | index index.html; 17 | location / { 18 | try_files $uri $uri/ /index.html; 19 | } 20 | location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|woff|woff2|ttf|eot|webp)$ { 21 | expires 30d; # Set caching for static assets 22 | add_header Cache-Control "public"; 23 | } 24 | 25 | gzip on; 26 | gzip_types text/plain application/javascript application/json text/css text/xml application/xml+rss; 27 | gzip_min_length 1000; 28 | } 29 | ``` 30 | 31 | After you've done that, ensure that the configuration is correct by running `nginx -t` and then reload Nginx 32 | (e.g. `systemctl reload nginx`). 33 | 34 | > **Note:** This configuration doesn't cover HTTPS, which is highly recommended to use. You can find more information 35 | about setting up HTTPS in the [Nginx documentation](https://nginx.org/en/docs/http/configuring_https_servers.html). 36 | 37 | ## Traefik (docker labels) 38 | 39 | If you are using Traefik as a reverse proxy, you can use the following labels, `docker-compose.yml` example: 40 | 41 | ```yaml 42 | services: 43 | synapse-admin: 44 | image: ghcr.io/etkecc/synapse-admin:latest 45 | restart: unless-stopped 46 | labels: 47 | - "traefik.enable=true" 48 | - "traefik.http.routers.synapse-admin.rule=Host(`example.com`)" 49 | ``` 50 | 51 | ## Other reverse proxies 52 | 53 | There is no examples for other reverse proxies yet, and so PRs are greatly appreciated. 54 | -------------------------------------------------------------------------------- /docs/system-users.md: -------------------------------------------------------------------------------- 1 | # System / Appservice-managed Users 2 | 3 | Inadvertently altering system user accounts managed by appservices (such as bridges) / system (such as bots) is a common issue. 4 | Editing, deleting, locking, or changing the passwords of these appservice-managed accounts can cause serious problems. 5 | To prevent this, we've added a new feature that blocks these types of modifications to such accounts, 6 | while still allowing other risk-free changes (changing display names and avatars). 7 | By defining a list of MXID regex patterns in the new `asManagedUsers` configuration setting, 8 | you can protect these accounts from accidental changes. 9 | 10 | ## Configuration 11 | 12 | The examples below contain the configuration settings to mark 13 | [Telegram bridge (mautrix-telegram)](https://github.com/mautrix/telegram), 14 | [Slack bridge (mautrix-slack)](https://github.com/mautrix/slack), 15 | and [Baibot](https://github.com/etkecc/baibot) users of `example.com` homeserver as appservice-managed users, 16 | just to illustrate the options to protect both specific MXIDs (as in the Baibot example) and all puppets of a bridge (as in the Telegram and Slack examples). 17 | 18 | [Configuration options](config.md) 19 | 20 | ### config.json 21 | 22 | ```json 23 | "asManagedUsers": [ 24 | "^@baibot:example\\.com$", 25 | "^@slackbot:example\\.com$", 26 | "^@slack_[a-zA-Z0-9\\-]+:example\\.com$", 27 | "^@telegrambot:example\\.com$", 28 | "^@telegram_[a-zA-Z0-9]+:example\\.com$" 29 | ] 30 | ``` 31 | 32 | ### `/.well-known/matrix/client` 33 | 34 | ```json 35 | "cc.etke.synapse-admin": { 36 | "asManagedUsers": [ 37 | "^@baibot:example\\.com$", 38 | "^@slackbot:example\\.com$", 39 | "^@slack_[a-zA-Z0-9\\-]+:example\\.com$", 40 | "^@telegrambot:example\\.com$", 41 | "^@telegram_[a-zA-Z0-9]+:example\\.com$" 42 | ] 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/user-badges.md: -------------------------------------------------------------------------------- 1 | # User Badges 2 | 3 | To help with identifying users with certain roles or permissions, we have implemented a badge system. 4 | These badges are displayed on the user's avatar and have a handy tooltip that explains what the badge means. 5 | 6 | ## Available Badges 7 | 8 | ### 🧙‍ You 9 | 10 | This badge is displayed on your user's avatar. 11 | Tooltip for this badge will contain additional information, e.g.: `You (Admin)`. 12 | 13 | ### 👑 Admin 14 | 15 | This badge is displayed on homeserver admins' avatars. 16 | Tooltip for this badge is `Admin`. 17 | 18 | ### 🛡️ Appservice/System-managed 19 | 20 | This badge is displayed on users that are managed by an appservices (or system), [more details](./system-users.md). 21 | Tooltip for this badge will contain additional information, e.g.: `System-managed (Bot)`. 22 | 23 | ### 🤖 Bot 24 | 25 | This badge is displayed on bots' avatars (users with the `user_type` set to `bot`). 26 | Tooltip for this badge is `Bot`. 27 | 28 | ### 📞 Support 29 | 30 | This badge is displayed on users that are part of the support team (users with the `user_type` set to `support`). 31 | Tooltip for this badge is `Support`. 32 | 33 | ### 👤 Regular User 34 | 35 | This badge is displayed on regular users' avatars. 36 | Tooltip for this badge is `Regular User`. 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Synapse Admin 15 | 116 | 117 | 118 | 119 |
120 |
121 |
Loading...
122 |
123 |
124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from "ts-jest"; 2 | 3 | const config: JestConfigWithTsJest = { 4 | preset: "ts-jest", 5 | testEnvironment: "jest-fixed-jsdom", 6 | collectCoverage: true, 7 | coveragePathIgnorePatterns: ["node_modules", "dist"], 8 | coverageDirectory: "/coverage/", 9 | coverageReporters: ["html", "text", "text-summary", "cobertura"], 10 | extensionsToTreatAsEsm: [".ts", ".tsx"], 11 | setupFilesAfterEnv: ["/src/jest.setup.ts"], 12 | transform: { 13 | "^.+\\.tsx?$": [ 14 | "ts-jest", 15 | { 16 | diagnostics: { 17 | ignoreCodes: [1343], 18 | }, 19 | astTransformers: { 20 | before: [ 21 | { 22 | path: "ts-jest-mock-import-meta", 23 | options: { metaObjectReplacement: { env: { BASE_URL: "/" } } }, 24 | }, 25 | ], 26 | }, 27 | }, 28 | ], 29 | }, 30 | }; 31 | export default config; 32 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Shows help 2 | default: 3 | @just --list --justfile {{ justfile() }} 4 | 5 | # build the app 6 | build: __install 7 | @yarn run build --base=./ 8 | 9 | update: 10 | yarn upgrade-interactive --latest 11 | 12 | # run the app in a development mode 13 | run: 14 | @yarn start --host 0.0.0.0 15 | 16 | # run dev stack and start the app in a development mode 17 | run-dev: 18 | @echo "Starting the database..." 19 | @docker-compose -f docker-compose-dev.yml up -d postgres 20 | @echo "Starting Synapse..." 21 | @docker-compose -f docker-compose-dev.yml up -d synapse 22 | @echo "Starting Element Web..." 23 | @docker-compose -f docker-compose-dev.yml up -d element 24 | @echo "Ensure admin user is registered..." 25 | @docker-compose -f docker-compose-dev.yml exec synapse register_new_matrix_user --admin -u admin -p admin -c /config/homeserver.yaml http://localhost:8008 || true 26 | @echo "Starting the app..." 27 | @yarn start --host 0.0.0.0 28 | 29 | # stop the dev stack 30 | stop-dev: 31 | @docker-compose -f docker-compose-dev.yml stop 32 | 33 | # register a user in the dev stack 34 | register-user localpart password *admin: 35 | 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 36 | 37 | # run yarn {fix,lint,test} commands 38 | test: 39 | @-yarn run fix 40 | @-yarn run lint 41 | @-yarn run test 42 | 43 | # run the app in a production mode 44 | run-prod: build 45 | @python -m http.server -d dist 1313 46 | 47 | # install the project 48 | __install: 49 | @yarn install --immutable --network-timeout=300000 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "synapse-admin", 3 | "version": "0.11.0", 4 | "description": "Admin GUI for the Matrix.org server Synapse", 5 | "type": "module", 6 | "author": "etke.cc (originally by Awesome Technologies Innovationslabor GmbH)", 7 | "license": "Apache-2.0", 8 | "homepage": ".", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/etkecc/synapse-admin" 12 | }, 13 | "devDependencies": { 14 | "@eslint/js": "^9.25.0", 15 | "@testing-library/dom": "^10.0.0", 16 | "@testing-library/jest-dom": "^6.6.3", 17 | "@testing-library/react": "^16.3.0", 18 | "@testing-library/user-event": "^14.6.1", 19 | "@types/jest": "^29.5.14", 20 | "@types/lodash": "^4.17.17", 21 | "@types/node": "^22.15.21", 22 | "@types/papaparse": "^5.3.16", 23 | "@types/react": "^19.1.5", 24 | "@typescript-eslint/eslint-plugin": "^8.32.0", 25 | "@typescript-eslint/parser": "^8.32.0", 26 | "@vitejs/plugin-react": "^4.5.0", 27 | "eslint": "^9.27.0", 28 | "eslint-config-prettier": "^10.1.5", 29 | "eslint-plugin-import": "^2.31.0", 30 | "eslint-plugin-jsx-a11y": "^6.10.2", 31 | "eslint-plugin-prettier": "^5.4.0", 32 | "eslint-plugin-unused-imports": "^4.1.4", 33 | "jest": "^29.7.0", 34 | "jest-environment-jsdom": "^29.7.0", 35 | "jest-fetch-mock": "^3.0.3", 36 | "prettier": "^3.5.3", 37 | "react-test-renderer": "^19.1.0", 38 | "ts-jest": "^29.3.4", 39 | "ts-node": "^10.9.2", 40 | "typescript": "^5.8.3", 41 | "typescript-eslint": "^8.32.1", 42 | "vite": "^6.3.5", 43 | "vite-plugin-version-mark": "^0.1.4" 44 | }, 45 | "dependencies": { 46 | "@emotion/react": "^11.14.0", 47 | "@emotion/styled": "^11.14.0", 48 | "@haleos/ra-language-german": "^1.0.0", 49 | "@haxqer/ra-language-chinese": "^4.16.2", 50 | "@mui/icons-material": "^7.1.0", 51 | "@mui/material": "^7.1.0", 52 | "@mui/utils": "^7.1.0", 53 | "@tanstack/react-query": "^5.77.1", 54 | "history": "^5.3.0", 55 | "jest-fixed-jsdom": "^0.0.9", 56 | "lodash": "^4.17.21", 57 | "papaparse": "^5.5.3", 58 | "ra-core": "^5.4.4", 59 | "ra-i18n-polyglot": "^5.4.4", 60 | "ra-language-english": "^5.4.4", 61 | "ra-language-farsi": "^5.1.0", 62 | "ra-language-french": "^5.8.2", 63 | "ra-language-italian": "^3.13.1", 64 | "ra-language-russian": "^5.4.3", 65 | "react": "^19.1.0", 66 | "react-admin": "^5.8.2", 67 | "react-dom": "^19.1.0", 68 | "react-hook-form": "^7.56.4", 69 | "react-is": "^19.1.0", 70 | "react-router": "^7.6.0", 71 | "react-router-dom": "^7.6.1", 72 | "ts-jest-mock-import-meta": "^1.3.0" 73 | }, 74 | "scripts": { 75 | "start": "vite serve", 76 | "build": "vite build", 77 | "lint": "ESLINT_USE_FLAT_CONFIG=false eslint --ignore-path .gitignore --ignore-pattern testdata/ --ext .ts,.tsx .", 78 | "fix": "yarn lint --fix", 79 | "test": "yarn jest", 80 | "test:watch": "yarn jest --watch" 81 | }, 82 | "eslintConfig": { 83 | "env": { 84 | "browser": true 85 | }, 86 | "plugins": [ 87 | "import", 88 | "prettier", 89 | "unused-imports", 90 | "@typescript-eslint" 91 | ], 92 | "extends": [ 93 | "eslint:recommended", 94 | "plugin:@typescript-eslint/recommended", 95 | "plugin:@typescript-eslint/stylistic", 96 | "plugin:import/typescript" 97 | ], 98 | "parser": "@typescript-eslint/parser", 99 | "parserOptions": { 100 | "project": "./tsconfig.eslint.json" 101 | }, 102 | "root": true, 103 | "rules": { 104 | "prettier/prettier": "error", 105 | "import/no-extraneous-dependencies": [ 106 | "error", 107 | { 108 | "devDependencies": [ 109 | "**/vite.config.ts", 110 | "**/jest.setup.ts", 111 | "**/*.test.ts", 112 | "**/*.test.tsx" 113 | ] 114 | } 115 | ], 116 | "import/order": [ 117 | "error", 118 | { 119 | "alphabetize": { 120 | "order": "asc", 121 | "caseInsensitive": false 122 | }, 123 | "newlines-between": "always", 124 | "groups": [ 125 | "external", 126 | "builtin", 127 | "internal", 128 | [ 129 | "parent", 130 | "sibling", 131 | "index" 132 | ] 133 | ] 134 | } 135 | ] 136 | } 137 | }, 138 | "prettier": { 139 | "printWidth": 120, 140 | "tabWidth": 2, 141 | "useTabs": false, 142 | "semi": true, 143 | "singleQuote": false, 144 | "trailingComma": "es5", 145 | "bracketSpacing": true, 146 | "arrowParens": "avoid" 147 | }, 148 | "browserslist": { 149 | "production": [ 150 | ">0.2%", 151 | "not dead", 152 | "not op_mini all" 153 | ], 154 | "development": [ 155 | "last 1 chrome version", 156 | "last 1 firefox version", 157 | "last 1 safari version" 158 | ] 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /public/config.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /public/data/example.csv: -------------------------------------------------------------------------------- 1 | id,displayname,password,is_guest,admin,deactivated 2 | testuser22,Jane Doe,secretpassword,false,true,false 3 | ,John Doe,,false,false,false 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/public/favicon.ico -------------------------------------------------------------------------------- /public/images/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/public/images/logo.webp -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /screenshots/auth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/auth.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-actions/page.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-actions/page.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-commands/panel.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-commands/panel.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-notifications/badge.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-notifications/badge.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-notifications/page.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-notifications/page.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-status/indicator-sidebar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-status/indicator-sidebar.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-status/indicator.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-status/indicator.webp -------------------------------------------------------------------------------- /screenshots/etke.cc/server-status/page.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/etke.cc/server-status/page.webp -------------------------------------------------------------------------------- /screenshots/screenshots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/etkecc/synapse-admin/c82d8653fd6500d57368d422d9c9a54c43377ca1/screenshots/screenshots.jpg -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import fetchMock from "jest-fetch-mock"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | fetchMock.enableMocks(); 5 | 6 | jest.mock("./synapse/authProvider", () => ({ 7 | __esModule: true, 8 | default: { 9 | logout: jest.fn().mockResolvedValue(undefined), 10 | }, 11 | })); 12 | 13 | import App from "./App"; 14 | 15 | describe("App", () => { 16 | beforeEach(() => { 17 | // Reset all mocks before each test 18 | fetchMock.resetMocks(); 19 | // Mock any fetch call to return empty JSON immediately 20 | fetchMock.mockResponseOnce(JSON.stringify({})); 21 | }); 22 | 23 | it("renders", async () => { 24 | render( 25 | 26 | 27 | 28 | ); 29 | 30 | await screen.findAllByText("Welcome to Synapse Admin"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 | import { merge } from "lodash"; 3 | import polyglotI18nProvider from "ra-i18n-polyglot"; 4 | import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin"; 5 | import { Route } from "react-router-dom"; 6 | 7 | import AdminLayout from "./components/AdminLayout"; 8 | import ServerActionsPage from "./components/etke.cc/ServerActionsPage"; 9 | import ServerNotificationsPage from "./components/etke.cc/ServerNotificationsPage"; 10 | import ServerStatusPage from "./components/etke.cc/ServerStatusPage"; 11 | import RecurringCommandEdit from "./components/etke.cc/schedules/components/recurring/RecurringCommandEdit"; 12 | import ScheduledCommandEdit from "./components/etke.cc/schedules/components/scheduled/ScheduledCommandEdit"; 13 | import ScheduledCommandShow from "./components/etke.cc/schedules/components/scheduled/ScheduledCommandShow"; 14 | import UserImport from "./components/user-import/UserImport"; 15 | import germanMessages from "./i18n/de"; 16 | import englishMessages from "./i18n/en"; 17 | import frenchMessages from "./i18n/fr"; 18 | import italianMessages from "./i18n/it"; 19 | import russianMessages from "./i18n/ru"; 20 | import chineseMessages from "./i18n/zh"; 21 | import LoginPage from "./pages/LoginPage"; 22 | import destinations from "./resources/destinations"; 23 | import registrationToken from "./resources/registration_tokens"; 24 | import reports from "./resources/reports"; 25 | import roomDirectory from "./resources/room_directory"; 26 | import rooms from "./resources/rooms"; 27 | import userMediaStats from "./resources/user_media_statistics"; 28 | import users from "./resources/users"; 29 | import authProvider from "./synapse/authProvider"; 30 | import dataProvider from "./synapse/dataProvider"; 31 | 32 | // TODO: Can we use lazy loading together with browser locale? 33 | const messages = { 34 | de: germanMessages, 35 | en: englishMessages, 36 | fr: frenchMessages, 37 | it: italianMessages, 38 | ru: russianMessages, 39 | zh: chineseMessages, 40 | }; 41 | const i18nProvider = polyglotI18nProvider( 42 | locale => (messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en), 43 | resolveBrowserLocale(), 44 | [ 45 | { locale: "en", name: "English" }, 46 | { locale: "de", name: "Deutsch" }, 47 | { locale: "fr", name: "Français" }, 48 | { locale: "it", name: "Italiano" }, 49 | { locale: "fa", name: "Persian(فارسی)" }, 50 | { locale: "ru", name: "Russian(Русский)" }, 51 | { locale: "zh", name: "简体中文" }, 52 | ] 53 | ); 54 | 55 | const queryClient = new QueryClient(); 56 | 57 | export const App = () => ( 58 | 59 | 68 | 69 | } /> 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | } /> 76 | } /> 77 | } /> 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | 100 | export default App; 101 | -------------------------------------------------------------------------------- /src/Context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | 3 | import { Config } from "./utils/config"; 4 | 5 | export const AppContext = createContext({} as Config); 6 | 7 | export const useAppContext = () => useContext(AppContext) as Config; 8 | -------------------------------------------------------------------------------- /src/components/AdminLayout.tsx: -------------------------------------------------------------------------------- 1 | import ManageHistoryIcon from "@mui/icons-material/ManageHistory"; 2 | import { useEffect, useState, Suspense } from "react"; 3 | import { 4 | CheckForApplicationUpdate, 5 | AppBar, 6 | TitlePortal, 7 | InspectorButton, 8 | Confirm, 9 | Layout, 10 | Logout, 11 | Menu, 12 | useLogout, 13 | UserMenu, 14 | useStore, 15 | } from "react-admin"; 16 | 17 | import Footer from "./Footer"; 18 | import { LoginMethod } from "../pages/LoginPage"; 19 | import { MenuItem, GetConfig, ClearConfig } from "../utils/config"; 20 | import { Icons, DefaultIcon } from "../utils/icons"; 21 | import { ServerNotificationsBadge } from "./etke.cc/ServerNotificationsBadge"; 22 | import { ServerProcessResponse, ServerStatusResponse } from "../synapse/dataProvider"; 23 | import ServerStatusBadge from "./etke.cc/ServerStatusBadge"; 24 | import { ServerStatusStyledBadge } from "./etke.cc/ServerStatusBadge"; 25 | 26 | const AdminUserMenu = () => { 27 | const [open, setOpen] = useState(false); 28 | const logout = useLogout(); 29 | const checkLoginType = (ev: React.MouseEvent) => { 30 | const loginType: LoginMethod = (localStorage.getItem("login_type") || "credentials") as LoginMethod; 31 | if (loginType === "accessToken") { 32 | ev.stopPropagation(); 33 | setOpen(true); 34 | } 35 | }; 36 | 37 | const handleConfirm = () => { 38 | setOpen(false); 39 | logout(); 40 | }; 41 | 42 | const handleDialogClose = () => { 43 | setOpen(false); 44 | ClearConfig(); 45 | window.location.reload(); 46 | }; 47 | 48 | return ( 49 | 50 |
51 | 52 |
53 | 62 |
63 | ); 64 | }; 65 | 66 | const AdminAppBar = () => { 67 | return ( 68 | }> 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | }; 76 | 77 | const AdminMenu = props => { 78 | const [menu, setMenu] = useState([] as MenuItem[]); 79 | const [etkeRoutesEnabled, setEtkeRoutesEnabled] = useState(false); 80 | useEffect(() => { 81 | setMenu(GetConfig().menu); 82 | if (GetConfig().etkeccAdmin) { 83 | setEtkeRoutesEnabled(true); 84 | } 85 | }, []); 86 | const [serverProcess, setServerProcess] = useStore("serverProcess", { 87 | command: "", 88 | locked_at: "", 89 | }); 90 | const [serverStatus, setServerStatus] = useStore("serverStatus", { 91 | success: false, 92 | ok: false, 93 | host: "", 94 | results: [], 95 | }); 96 | 97 | return ( 98 | 99 | {etkeRoutesEnabled && ( 100 | 111 | } 112 | primaryText="Server Status" 113 | /> 114 | )} 115 | {etkeRoutesEnabled && ( 116 | } 120 | primaryText="Server Actions" 121 | /> 122 | )} 123 | 124 | {menu && 125 | menu.map((item, index) => { 126 | const { url, icon, label } = item; 127 | const IconComponent = Icons[icon] as React.ComponentType | undefined; 128 | 129 | return ( 130 | 131 | : } 136 | onClick={props.onMenuClick} 137 | /> 138 | 139 | ); 140 | })} 141 | 142 | ); 143 | }; 144 | 145 | export const AdminLayout = ({ children }) => { 146 | return ( 147 | <> 148 | 161 | {children} 162 | 163 | 164 |