From a2e47cb79361b3db8411bd821958e3969a79e94c Mon Sep 17 00:00:00 2001 From: Gavin Mogan Date: Wed, 17 Apr 2024 11:32:41 -0700 Subject: [PATCH 01/33] Add source urls to docker so tools can find sourcecode (#506) For tools like renovate or dependabot, they like to put changelog notes in PRs updating deps. Having the labels allows the tools to link it back to sourcecode and share commits/release notes --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 12b6ed1..08515d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Builder FROM node:lts as builder - +LABEL org.opencontainers.image.url=https://github.com/Awesome-Technologies/synapse-admin org.opencontainers.image.source=https://github.com/Awesome-Technologies/synapse-admin ARG REACT_APP_SERVER WORKDIR /src From 33d29e01b1c90990039fb39781455d5a4c63b5ae Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 16 Apr 2024 12:45:51 +0200 Subject: [PATCH 02/33] Add authProvider test Change-Id: Ia5acce659a386437687e38ae03d578e3bccb9324 --- src/synapse/authProvider.test.js | 137 +++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/synapse/authProvider.test.js diff --git a/src/synapse/authProvider.test.js b/src/synapse/authProvider.test.js new file mode 100644 index 0000000..1fecbc3 --- /dev/null +++ b/src/synapse/authProvider.test.js @@ -0,0 +1,137 @@ +import { waitFor } from "@testing-library/react"; + +import authProvider from "./authProvider"; + +describe("authProvider", () => { + beforeEach(() => { + fetch.resetMocks(); + localStorage.clear(); + }); + + describe("login", () => { + it("should successfully login with username and password", async () => { + fetch.once( + JSON.stringify({ + home_server: "example.com", + user_id: "@user:example.com", + access_token: "foobar", + device_id: "some_device", + }) + ); + + const ret = await authProvider.login({ + base_url: "http://example.com", + username: "@user:example.com", + password: "secret", + }); + + expect(ret).toBe(undefined); + expect(fetch).toBeCalledWith( + "http://example.com/_matrix/client/r0/login", + { + body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.password","user":"@user:example.com","password":"secret"}', + headers: new Headers({ + Accept: ["application/json"], + "Content-Type": ["application/json"], + }), + method: "POST", + } + ); + expect(localStorage.getItem("base_url")).toEqual("http://example.com"); + expect(localStorage.getItem("user_id")).toEqual("@user:example.com"); + expect(localStorage.getItem("access_token")).toEqual("foobar"); + expect(localStorage.getItem("device_id")).toEqual("some_device"); + }); + }); + + it("should successfully login with token", async () => { + fetch.once( + JSON.stringify({ + home_server: "example.com", + user_id: "@user:example.com", + access_token: "foobar", + device_id: "some_device", + }) + ); + + const ret = await authProvider.login({ + base_url: "https://example.com/", + loginToken: "login_token", + }); + + expect(ret).toBe(undefined); + expect(fetch).toBeCalledWith( + "https://example.com/_matrix/client/r0/login", + { + body: '{"device_id":null,"initial_device_display_name":"Synapse Admin","type":"m.login.token","token":"login_token"}', + headers: new Headers({ + Accept: ["application/json"], + "Content-Type": ["application/json"], + }), + method: "POST", + } + ); + expect(localStorage.getItem("base_url")).toEqual("https://example.com"); + expect(localStorage.getItem("user_id")).toEqual("@user:example.com"); + expect(localStorage.getItem("access_token")).toEqual("foobar"); + expect(localStorage.getItem("device_id")).toEqual("some_device"); + }); + + describe("logout", () => { + it("should remove the access_token from localStorage", async () => { + localStorage.setItem("base_url", "example.com"); + localStorage.setItem("access_token", "foo"); + fetch.mockResponse(JSON.stringify({})); + + await authProvider.logout(); + + expect(fetch).toBeCalledWith("example.com/_matrix/client/r0/logout", { + headers: new Headers({ + Accept: ["application/json"], + Authorization: ["Bearer foo"], + }), + method: "POST", + user: { authenticated: true, token: "Bearer foo" }, + }); + waitFor(() => expect(localStorage.getItem("access_token")).toBeNull()); + }); + }); + + describe("checkError", () => { + it("should resolve if error.status is not 401 or 403", async () => { + await expect( + authProvider.checkError({ status: 200 }) + ).resolves.toBeUndefined(); + }); + + it("should reject if error.status is 401", async () => { + await expect( + authProvider.checkError({ status: 401 }) + ).rejects.toBeUndefined(); + }); + + it("should reject if error.status is 403", async () => { + await expect( + authProvider.checkError({ status: 403 }) + ).rejects.toBeUndefined(); + }); + }); + + describe("checkAuth", () => { + it("should reject when not logged in", async () => { + await expect(authProvider.checkAuth({})).rejects.toBeUndefined(); + }); + + it("should resolve when logged in", async () => { + localStorage.setItem("access_token", "foobar"); + + await expect(authProvider.checkAuth({})).resolves.toBeUndefined(); + }); + }); + + describe("getPermissions", () => { + it("should do nothing", async () => { + await expect(authProvider.getPermissions()).resolves.toBeUndefined(); + }); + }); +}); From 0b4f3a60c0bc386e5dbf88faa61062e216d0700b Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 16 Apr 2024 13:16:09 +0200 Subject: [PATCH 03/33] Make login and logout in authProvider async Change-Id: I6bfb1c7a5a3c5a43f9fa622e87d9d487a95a0b6e --- src/synapse/authProvider.js | 21 +++++++++------------ src/synapse/authProvider.test.js | 4 +--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/synapse/authProvider.js b/src/synapse/authProvider.js index 723f155..159add5 100644 --- a/src/synapse/authProvider.js +++ b/src/synapse/authProvider.js @@ -2,7 +2,7 @@ import { fetchUtils } from "react-admin"; const authProvider = { // called when the user attempts to log in - login: ({ base_url, username, password, loginToken }) => { + login: async ({ base_url, username, password, loginToken }) => { // force homeserver for protection in case the form is manipulated base_url = process.env.REACT_APP_SERVER || base_url; @@ -38,15 +38,14 @@ const authProvider = { const decoded_base_url = window.decodeURIComponent(base_url); const login_api_url = decoded_base_url + "/_matrix/client/r0/login"; - return fetchUtils.fetchJson(login_api_url, options).then(({ json }) => { - localStorage.setItem("home_server", json.home_server); - localStorage.setItem("user_id", json.user_id); - localStorage.setItem("access_token", json.access_token); - localStorage.setItem("device_id", json.device_id); - }); + const { json } = await fetchUtils.fetchJson(login_api_url, options); + localStorage.setItem("home_server", json.home_server); + localStorage.setItem("user_id", json.user_id); + localStorage.setItem("access_token", json.access_token); + localStorage.setItem("device_id", json.device_id); }, // called when the user clicks on the logout button - logout: () => { + logout: async () => { console.log("logout"); const logout_api_url = @@ -62,11 +61,9 @@ const authProvider = { }; if (typeof access_token === "string") { - fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => { - localStorage.removeItem("access_token"); - }); + await fetchUtils.fetchJson(logout_api_url, options); + localStorage.removeItem("access_token"); } - return Promise.resolve(); }, // called when the API returns an error checkError: ({ status }) => { diff --git a/src/synapse/authProvider.test.js b/src/synapse/authProvider.test.js index 1fecbc3..c55ece0 100644 --- a/src/synapse/authProvider.test.js +++ b/src/synapse/authProvider.test.js @@ -1,5 +1,3 @@ -import { waitFor } from "@testing-library/react"; - import authProvider from "./authProvider"; describe("authProvider", () => { @@ -93,7 +91,7 @@ describe("authProvider", () => { method: "POST", user: { authenticated: true, token: "Bearer foo" }, }); - waitFor(() => expect(localStorage.getItem("access_token")).toBeNull()); + expect(localStorage.getItem("access_token")).toBeNull(); }); }); From 1acffdb6182b3f6f3456dd7a6f863cc70804d928 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 16 Apr 2024 14:11:18 +0200 Subject: [PATCH 04/33] Make functions in dataProvider async Change-Id: Iab36ba6379340e47e7d58b1b2d882cd7cc111f41 --- src/synapse/dataProvider.js | 107 ++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js index dfa346a..9247aa6 100644 --- a/src/synapse/dataProvider.js +++ b/src/synapse/dataProvider.js @@ -348,7 +348,7 @@ function getSearchOrder(order) { } const dataProvider = { - getList: (resource, params) => { + getList: async (resource, params) => { console.log("getList " + resource); const { user_id, @@ -383,13 +383,14 @@ const dataProvider = { const endpoint_url = homeserver + res.path; const url = `${endpoint_url}?${stringify(query)}`; - return jsonClient(url).then(({ json }) => ({ + const { json } = await jsonClient(url); + return { data: json[res.data].map(res.map), total: res.total(json, from, perPage), - })); + }; }, - getOne: (resource, params) => { + getOne: async (resource, params) => { console.log("getOne " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -397,14 +398,13 @@ const dataProvider = { const res = resourceMap[resource]; const endpoint_url = homeserver + res.path; - return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`).then( - ({ json }) => ({ - data: res.map(json), - }) + const { json } = await jsonClient( + `${endpoint_url}/${encodeURIComponent(params.id)}` ); + return { data: res.map(json) }; }, - getMany: (resource, params) => { + getMany: async (resource, params) => { console.log("getMany " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -412,17 +412,18 @@ const dataProvider = { const res = resourceMap[resource]; const endpoint_url = homeserver + res.path; - return Promise.all( + const responses = await Promise.all( params.ids.map(id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`) ) - ).then(responses => ({ + ); + return { data: responses.map(({ json }) => res.map(json)), total: responses.length, - })); + }; }, - getManyReference: (resource, params) => { + getManyReference: async (resource, params) => { console.log("getManyReference " + resource); const { page, perPage } = params.pagination; const { field, order } = params.sort; @@ -442,13 +443,14 @@ const dataProvider = { const ref = res["reference"](params.id); const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`; - return jsonClient(endpoint_url).then(({ headers, json }) => ({ + const { json } = await jsonClient(endpoint_url); + return { data: json[res.data].map(res.map), total: res.total(json, from, perPage), - })); + }; }, - update: (resource, params) => { + update: async (resource, params) => { console.log("update " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -456,15 +458,17 @@ const dataProvider = { const res = resourceMap[resource]; const endpoint_url = homeserver + res.path; - return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`, { - method: "PUT", - body: JSON.stringify(params.data, filterNullValues), - }).then(({ json }) => ({ - data: res.map(json), - })); + const { json } = await jsonClient( + `${endpoint_url}/${encodeURIComponent(params.id)}`, + { + method: "PUT", + body: JSON.stringify(params.data, filterNullValues), + } + ); + return { data: res.map(json) }; }, - updateMany: (resource, params) => { + updateMany: async (resource, params) => { console.log("updateMany " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -472,7 +476,7 @@ const dataProvider = { const res = resourceMap[resource]; const endpoint_url = homeserver + res.path; - return Promise.all( + const responses = await Promise.all( params.ids.map( id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`), { @@ -480,12 +484,11 @@ const dataProvider = { body: JSON.stringify(params.data, filterNullValues), } ) - ).then(responses => ({ - data: responses.map(({ json }) => json), - })); + ); + return { data: responses.map(({ json }) => json) }; }, - create: (resource, params) => { + create: async (resource, params) => { console.log("create " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -495,15 +498,14 @@ const dataProvider = { const create = res["create"](params.data); const endpoint_url = homeserver + create.endpoint; - return jsonClient(endpoint_url, { + const { json } = await jsonClient(endpoint_url, { method: create.method, body: JSON.stringify(create.body, filterNullValues), - }).then(({ json }) => ({ - data: res.map(json), - })); + }); + return { data: res.map(json) }; }, - createMany: (resource, params) => { + createMany: async (resource, params) => { console.log("createMany " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -511,7 +513,7 @@ const dataProvider = { const res = resourceMap[resource]; if (!("create" in res)) return Promise.reject(); - return Promise.all( + const responses = await Promise.all( params.ids.map(id => { params.data.id = id; const cre = res["create"](params.data); @@ -521,12 +523,11 @@ const dataProvider = { body: JSON.stringify(cre.body, filterNullValues), }); }) - ).then(responses => ({ - data: responses.map(({ json }) => json), - })); + ); + return { data: responses.map(({ json }) => json) }; }, - delete: (resource, params) => { + delete: async (resource, params) => { console.log("delete " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -536,24 +537,22 @@ const dataProvider = { if ("delete" in res) { const del = res["delete"](params); const endpoint_url = homeserver + del.endpoint; - return jsonClient(endpoint_url, { + const { json } = await jsonClient(endpoint_url, { method: "method" in del ? del.method : "DELETE", body: "body" in del ? JSON.stringify(del.body) : null, - }).then(({ json }) => ({ - data: json, - })); + }); + return { data: json }; } else { const endpoint_url = homeserver + res.path; - return jsonClient(`${endpoint_url}/${params.id}`, { + const { json } = await jsonClient(`${endpoint_url}/${params.id}`, { method: "DELETE", body: JSON.stringify(params.previousData, filterNullValues), - }).then(({ json }) => ({ - data: json, - })); + }); + return { data: json }; } }, - deleteMany: (resource, params) => { + deleteMany: async (resource, params) => { console.log("deleteMany " + resource); const homeserver = localStorage.getItem("base_url"); if (!homeserver || !(resource in resourceMap)) return Promise.reject(); @@ -561,7 +560,7 @@ const dataProvider = { const res = resourceMap[resource]; if ("delete" in res) { - return Promise.all( + const responses = await Promise.all( params.ids.map(id => { const del = res["delete"]({ ...params, id: id }); const endpoint_url = homeserver + del.endpoint; @@ -570,21 +569,21 @@ const dataProvider = { body: "body" in del ? JSON.stringify(del.body) : null, }); }) - ).then(responses => ({ + ); + return { data: responses.map(({ json }) => json), - })); + }; } else { const endpoint_url = homeserver + res.path; - return Promise.all( + const responses = await Promise.all( params.ids.map(id => jsonClient(`${endpoint_url}/${id}`, { method: "DELETE", body: JSON.stringify(params.data, filterNullValues), }) ) - ).then(responses => ({ - data: responses.map(({ json }) => json), - })); + ); + return { data: responses.map(({ json }) => json) }; } }, }; From 25020c2d5bda1881b14019fcda421f28fc773854 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Wed, 17 Apr 2024 15:16:57 +0200 Subject: [PATCH 05/33] Remove unused function "renderInput" Seems to be obsolete since react-admin v4. Change-Id: I9f1d528a43510efd61befd23a05d1c8ebf40ddfd --- src/components/LoginPage.jsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/components/LoginPage.jsx b/src/components/LoginPage.jsx index aa1cd03..b177efa 100644 --- a/src/components/LoginPage.jsx +++ b/src/components/LoginPage.jsx @@ -21,7 +21,6 @@ import { CircularProgress, MenuItem, Select, - TextField, Typography, } from "@mui/material"; import { styled } from "@mui/material/styles"; @@ -127,20 +126,6 @@ const LoginPage = () => { } } - const renderInput = ({ - meta: { touched, error } = {}, - input: { ...inputProps }, - ...props - }) => ( - - ); - const validateBaseUrl = value => { if (!value.match(/^(http|https):\/\//)) { return translate("synapseadmin.auth.protocol_error"); @@ -219,7 +204,6 @@ const LoginPage = () => { { { Date: Wed, 17 Apr 2024 11:25:17 +0200 Subject: [PATCH 06/33] Show Matrix specs supported by the homeserver Change-Id: I01c110fb4b3de4de49b34f290c91c8bf424521fe --- src/components/LoginPage.jsx | 42 +++++++++++++++++++++++++----------- src/i18n/de.js | 1 + src/i18n/en.js | 1 + src/synapse/synapse.js | 7 ++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/LoginPage.jsx b/src/components/LoginPage.jsx index b177efa..b540515 100644 --- a/src/components/LoginPage.jsx +++ b/src/components/LoginPage.jsx @@ -27,6 +27,7 @@ import { styled } from "@mui/material/styles"; import LockIcon from "@mui/icons-material/Lock"; import { getServerVersion, + getSupportedFeatures, getSupportedLoginFlows, getWellKnownUrl, isValidBaseUrl, @@ -36,7 +37,7 @@ import { const FormBox = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", - minHeight: "calc(100vh - 1em)", + minHeight: "calc(100vh - 1rem)", alignItems: "center", justifyContent: "flex-start", background: "url(./images/floating-cogs.svg)", @@ -45,12 +46,12 @@ const FormBox = styled(Box)(({ theme }) => ({ backgroundSize: "cover", [`& .card`]: { - minWidth: "30em", - marginTop: "6em", - marginBottom: "6em", + width: "30rem", + marginTop: "6rem", + marginBottom: "6rem", }, [`& .avatar`]: { - margin: "1em", + margin: "1rem", display: "flex", justifyContent: "center", }, @@ -59,24 +60,31 @@ const FormBox = styled(Box)(({ theme }) => ({ }, [`& .hint`]: { marginTop: "1em", + marginBottom: "1em", display: "flex", justifyContent: "center", color: theme.palette.grey[600], }, [`& .form`]: { - padding: "0 1em 1em 1em", + padding: "0 1rem 1rem 1rem", }, - [`& .input`]: { - marginTop: "1em", + [`& .select`]: { + marginBottom: "2rem", }, [`& .actions`]: { - padding: "0 1em 1em 1em", + padding: "0 1rem 1rem 1rem", }, [`& .serverVersion`]: { color: theme.palette.grey[500], fontFamily: "Roboto, Helvetica, Arial, sans-serif", - marginBottom: "1em", - marginLeft: "0.5em", + marginLeft: "0.5rem", + }, + [`& .matrixVersions`]: { + color: theme.palette.grey[500], + fontFamily: "Roboto, Helvetica, Arial, sans-serif", + fontSize: "0.8rem", + marginBottom: "1rem", + marginLeft: "0.5rem", }, })); @@ -164,6 +172,7 @@ const LoginPage = () => { const UserData = ({ formData }) => { const form = useFormContext(); const [serverVersion, setServerVersion] = useState(""); + const [matrixVersions, setMatrixVersions] = useState(""); const handleUsernameChange = _ => { if (formData.base_url || cfg_base_url) return; @@ -185,6 +194,14 @@ const LoginPage = () => { ) .catch(() => setServerVersion("")); + getSupportedFeatures(formData.base_url) + .then(features => + setMatrixVersions( + `${translate("synapseadmin.auth.supports_specs")} ${features.versions.join(", ")}` + ) + ) + .catch(() => setMatrixVersions("")); + // Set SSO Url getSupportedLoginFlows(formData.base_url) .then(loginFlows => { @@ -237,6 +254,7 @@ const LoginPage = () => { /> {serverVersion} + {matrixVersions} ); }; @@ -267,7 +285,7 @@ const LoginPage = () => { }} fullWidth disabled={loading} - className="input" + className="select" > Deutsch English diff --git a/src/i18n/de.js b/src/i18n/de.js index 8ba31d3..0471137 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -7,6 +7,7 @@ const de = { base_url: "Heimserver URL", welcome: "Willkommen bei Synapse-admin", server_version: "Synapse Version", + supports_specs: "unterstützt Matrix-Specs", username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'", protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen", url_error: "Keine gültige Matrix Server URL", diff --git a/src/i18n/en.js b/src/i18n/en.js index 9586508..a7d234b 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -7,6 +7,7 @@ const en = { base_url: "Homeserver URL", welcome: "Welcome to Synapse-admin", server_version: "Synapse version", + supports_specs: "supports Matrix specs", username_error: "Please enter fully qualified user ID: '@user:domain'", protocol_error: "URL has to start with 'http://' or 'https://'", url_error: "Not a valid Matrix server URL", diff --git a/src/synapse/synapse.js b/src/synapse/synapse.js index 64003ba..ddd4136 100644 --- a/src/synapse/synapse.js +++ b/src/synapse/synapse.js @@ -36,6 +36,13 @@ export const getServerVersion = async baseUrl => { return response.json.server_version; }; +/** Get supported Matrix features */ +export const getSupportedFeatures = async baseUrl => { + const versionUrl = `${baseUrl}/_matrix/client/versions`; + const response = await fetchUtils.fetchJson(versionUrl, { method: "GET" }); + return response.json; +}; + /** * Get supported login flows * @param baseUrl the base URL of the homeserver From 03c4955ef70435500eb0e2b82b9feb0b88c55573 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Thu, 18 Apr 2024 16:37:56 +0200 Subject: [PATCH 07/33] Push docker images also to ghcr.io Fixes #350. Change-Id: Ifdb7e4e7fda46efd0ed9e760587033f52ff4a130 --- .github/workflows/docker-release.yml | 48 ++++++++++++++--------- .github/workflows/test-docker-image.yml | 51 ------------------------- 2 files changed, 29 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/test-docker-image.yml diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 682b201..e390137 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -1,51 +1,61 @@ -name: Create docker image(s) and push to docker hub +name: Create docker image(s) and push to docker hub and ghcr.io +# see https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-docker-hub-and-github-packages on: push: # Sequence of patterns matched against refs/heads # prettier-ignore - branches: + branches: # Push events on master branch - master # Sequence of patterns matched against refs/tags - tags: + tags: - '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag jobs: docker: + name: Push Docker image to multiple registries runs-on: ubuntu-latest + permissions: + packages: write + contents: read steps: - name: Checkout uses: actions/checkout@v4 + - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Calculate docker image tag - id: set-tag - run: | - case "${GITHUB_REF}" in - refs/heads/master|refs/heads/main) - tag=latest - ;; - refs/tags/*) - tag=${GITHUB_REF#refs/tags/} - ;; - *) - tag=${GITHUB_SHA} - ;; - esac - echo "::set-output name=tag::$tag" + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + awesometechnologies/synapse-admin + ghcr.io/${{ github.repository }} + - name: Build and Push Tag uses: docker/build-push-action@v5 with: context: . push: true - tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}" + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/test-docker-image.yml b/.github/workflows/test-docker-image.yml deleted file mode 100644 index 778385d..0000000 --- a/.github/workflows/test-docker-image.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Test docker image creation - -on: - push: - # Sequence of patterns matched against refs/heads - # prettier-ignore - branches: - # Push events on branch fix_docker_cd - - fix_docker_cd - # Sequence of patterns matched against refs/tags - tags: - - '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag - -jobs: - docker: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Calculate docker image tag - id: set-tag - run: | - case "${GITHUB_REF}" in - refs/heads/master|refs/heads/main) - tag=latest - ;; - refs/tags/*) - tag=${GITHUB_REF#refs/tags/} - ;; - *) - tag=${GITHUB_SHA} - ;; - esac - echo "::set-output name=tag::$tag" - - name: Build and Push Tag - uses: docker/build-push-action@v5 - with: - context: . - push: false - tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}" - platforms: linux/amd64,linux/arm64 From 881760c8d893dade0e91f33f8f8cbc08a0fed065 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Thu, 18 Apr 2024 21:11:41 +0200 Subject: [PATCH 08/33] Fetch tags in github workflows Tags are required to construct the version information. Change-Id: Ic1af3e8f50eafafcc8a0c3ca37f362d6bd05e116 --- .github/workflows/docker-release.yml | 3 +++ .github/workflows/edge_ghpage.yml | 3 +++ .github/workflows/github-release.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index e390137..afb1d58 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -23,6 +23,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 100 + fetch-tags: true - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/edge_ghpage.yml b/.github/workflows/edge_ghpage.yml index ea8cfdb..23ec1ae 100644 --- a/.github/workflows/edge_ghpage.yml +++ b/.github/workflows/edge_ghpage.yml @@ -11,6 +11,9 @@ jobs: steps: - name: Checkout 🛎️ uses: actions/checkout@v4 + with: + fetch-depth: 100 + fetch-tags: true - uses: actions/setup-node@v4 with: node-version: "18" diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index c12cbdc..2ab4c72 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -14,6 +14,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 100 + fetch-tags: true - uses: actions/setup-node@v4 with: node-version: "18" From 6bdeadcc3edb03224d3870663e40e12ab1cbedd0 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Fri, 19 Apr 2024 09:36:09 +0200 Subject: [PATCH 09/33] Use node 20 in github workflows Change-Id: I5850572bf604edd91b8a6f4e7b34ebf788e5c65b --- .github/workflows/edge_ghpage.yml | 2 +- .github/workflows/github-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/edge_ghpage.yml b/.github/workflows/edge_ghpage.yml index 23ec1ae..be91f65 100644 --- a/.github/workflows/edge_ghpage.yml +++ b/.github/workflows/edge_ghpage.yml @@ -16,7 +16,7 @@ jobs: fetch-tags: true - uses: actions/setup-node@v4 with: - node-version: "18" + node-version: "20" - name: Install and Build 🔧 run: | yarn install --immutable diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 2ab4c72..99107ac 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -19,7 +19,7 @@ jobs: fetch-tags: true - uses: actions/setup-node@v4 with: - node-version: "18" + node-version: "20" - run: yarn install --immutable - run: yarn build - run: | From df1fbbc16bd2b15c290cbe2df9e5e0f943be8588 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Fri, 19 Apr 2024 09:36:44 +0200 Subject: [PATCH 10/33] Use nginx:stable-alpine as base image for docker container Change-Id: Ibc9b430cb79b8c05b111d3993fc3b1543853a515 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 08515d0..f936a8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build # App -FROM nginx:alpine +FROM nginx:stable-alpine COPY --from=builder /src/build /app From 6d9abe85b032d61158c03e33d562cab6823691c7 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 09:55:00 +0200 Subject: [PATCH 11/33] Bump version to 0.9.3 Change-Id: Ib6bf464fb08d8119cbbcb0d387545f3e58642ade --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ce0152..80985b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "synapse-admin", - "version": "0.9.2", + "version": "0.9.3", "description": "Admin GUI for the Matrix.org server Synapse", "author": "Awesome Technologies Innovationslabor GmbH", "license": "Apache-2.0", From 400e9aa41679421db9a93ad2d8fa6e3e060b0a93 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 10:16:42 +0200 Subject: [PATCH 12/33] Fix fetching tags in github workflows The actions/checkout step has some bugs: https://github.com/actions/checkout/issues/1467 Change-Id: I6bd88433425657081c2033b55bf01587979983df --- .github/workflows/docker-release.yml | 1 - .github/workflows/github-release.yml | 3 --- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index afb1d58..6c0f6a8 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -25,7 +25,6 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 100 - fetch-tags: true - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 99107ac..a5ece09 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -14,9 +14,6 @@ jobs: steps: - uses: actions/checkout@v4 - with: - fetch-depth: 100 - fetch-tags: true - uses: actions/setup-node@v4 with: node-version: "20" From 028babc8858a85c6a3abc1e36483d41b5d8402ea Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 10:17:05 +0200 Subject: [PATCH 13/33] Bump version to 0.9.4 Change-Id: Ic91bd40e242605986abc48982a5bec8a95c20c7a --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80985b8..82b0f12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "synapse-admin", - "version": "0.9.3", + "version": "0.9.4", "description": "Admin GUI for the Matrix.org server Synapse", "author": "Awesome Technologies Innovationslabor GmbH", "license": "Apache-2.0", From 441f7749a286b2c6b06381c97870802b18824ad2 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 10:57:24 +0200 Subject: [PATCH 14/33] Get available translations from context in LoginPage Change-Id: Ie9febb82c0c93ba797241a4e6e22c6b6e72c6b02 --- src/App.jsx | 10 +++++++++- src/components/LoginPage.jsx | 17 ++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 1eff2a7..48be314 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -34,7 +34,15 @@ const messages = { }; const i18nProvider = polyglotI18nProvider( locale => (messages[locale] ? messages[locale] : messages.en), - resolveBrowserLocale() + resolveBrowserLocale(), + [ + { locale: "en", name: "English" }, + { locale: "de", name: "Deutsch" }, + { locale: "fr", name: "Français" }, + { locale: "it", name: "Italiano" }, + { locale: "fa", name: "Persian(فارسی)" }, + { locale: "zh", name: "简体中文" }, + ] ); const App = () => ( diff --git a/src/components/LoginPage.jsx b/src/components/LoginPage.jsx index b540515..4d0497f 100644 --- a/src/components/LoginPage.jsx +++ b/src/components/LoginPage.jsx @@ -10,6 +10,7 @@ import { useTranslate, PasswordInput, TextInput, + useLocales, } from "react-admin"; import { useFormContext } from "react-hook-form"; import { @@ -94,6 +95,7 @@ const LoginPage = () => { const [loading, setLoading] = useState(false); const [supportPassAuth, setSupportPassAuth] = useState(true); const [locale, setLocale] = useLocaleState(); + const locales = useLocales(); const translate = useTranslate(); const base_url = localStorage.getItem("base_url"); const cfg_base_url = process.env.REACT_APP_SERVER; @@ -280,19 +282,16 @@ const LoginPage = () => { {formDataProps => } From e666c9c7bd4e661bb21ad3a45f9fe93ee2c84811 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 10:57:44 +0200 Subject: [PATCH 15/33] Fallback to english if no translation in the current language is available Change-Id: I94ecf5f2d742b1653177c49cef6b1b7fd6e96df0 --- src/App.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index 48be314..5c1a88d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,6 +6,7 @@ import { resolveBrowserLocale, } from "react-admin"; import polyglotI18nProvider from "ra-i18n-polyglot"; +import merge from "lodash/merge"; import authProvider from "./synapse/authProvider"; import dataProvider from "./synapse/dataProvider"; import users from "./components/users"; @@ -33,7 +34,8 @@ const messages = { zh: chineseMessages, }; const i18nProvider = polyglotI18nProvider( - locale => (messages[locale] ? messages[locale] : messages.en), + locale => + messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en, resolveBrowserLocale(), [ { locale: "en", name: "English" }, From 5f1dfc95c7e3990de1b230246b6cff78d1c75d1d Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 11:11:04 +0200 Subject: [PATCH 16/33] Remove obsolete react-admin translations for german These are now provided by ra-language-german. Change-Id: I5f820139fe5322f488398abf879582508507d38d --- src/i18n/de.js | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/i18n/de.js b/src/i18n/de.js index 0471137..7ab6cf2 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -389,37 +389,5 @@ const de = { helper: { length: "Länge des Tokens, wenn kein Token vorgegeben wird." }, }, }, - ra: { - ...germanMessages.ra, - action: { - ...germanMessages.ra.action, - unselect: "Abwählen", - }, - auth: { - ...germanMessages.ra.auth, - auth_check_error: "Anmeldung fehlgeschlagen", - }, - input: { - ...germanMessages.ra.input, - password: { - ...germanMessages.ra.input.password, - toggle_hidden: "Anzeigen", - toggle_visible: "Verstecken", - }, - }, - notification: { - ...germanMessages.ra.notification, - logged_out: "Abgemeldet", - }, - page: { - ...germanMessages.ra.page, - empty: "Keine Einträge vorhanden", - invite: "", - }, - navigation: { - ...germanMessages.ra.navigation, - skip_nav: "Zum Inhalt springen", - }, - }, }; export default de; From 0986d95de24e68b037af024a3f4fceea353837de Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 22 Apr 2024 13:43:46 +0200 Subject: [PATCH 17/33] Use officially recommended language packages See https://marmelab.com/react-admin/TranslationLocales.html Change-Id: I1e29aef26e8cf02b34cd4c0ba105c3bd68e8637e --- package.json | 4 ++-- src/i18n/de.js | 4 ++-- src/i18n/zh.js | 2 +- yarn.lock | 33 ++++++++++++++++++++++----------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 82b0f12..8ad8835 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "prettier": "^3.2.5" }, "dependencies": { + "@haleos/ra-language-german": "^1.0.0", + "@haxqer/ra-language-chinese": "^4.16.2", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.15", "@mui/styles": "^5.15.15", "papaparse": "^5.4.1", - "ra-language-chinese": "^2.0.10", "ra-language-french": "^4.16.15", - "ra-language-german": "^3.13.4", "ra-language-italian": "^3.13.1", "ra-language-farsi": "^4.2.0", "react": "^18.0.0", diff --git a/src/i18n/de.js b/src/i18n/de.js index 7ab6cf2..c7d8014 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -1,7 +1,7 @@ -import germanMessages from "ra-language-german"; +import { formalGermanMessages } from "@haleos/ra-language-german"; const de = { - ...germanMessages, + ...formalGermanMessages, synapseadmin: { auth: { base_url: "Heimserver URL", diff --git a/src/i18n/zh.js b/src/i18n/zh.js index 7a9b4e7..77c56f1 100644 --- a/src/i18n/zh.js +++ b/src/i18n/zh.js @@ -1,4 +1,4 @@ -import chineseMessages from "ra-language-chinese"; +import chineseMessages from "@haxqer/ra-language-chinese"; const zh = { ...chineseMessages, diff --git a/yarn.lock b/yarn.lock index 7b39162..4bbc592 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1435,6 +1435,22 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== +"@haleos/ra-language-german@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@haleos/ra-language-german/-/ra-language-german-1.0.0.tgz#5ea48d9f301da678fc5021ec0c2ac2dd241f7702" + integrity sha512-dGlNhxQklsqC6l5vjw9lhhesNppeKxvZdZiEpOjmgxoA3+FAffqwILnghicKQhWBzNUjBXwdcL4Cc4RHju3Dgw== + dependencies: + lodash "^4.17.21" + ra-core "^4.11.2" + type-fest "^3.12.0" + +"@haxqer/ra-language-chinese@^4.16.2": + version "4.16.2" + resolved "https://registry.yarnpkg.com/@haxqer/ra-language-chinese/-/ra-language-chinese-4.16.2.tgz#2e971a95612de8c1d47ab1257f57044cc4df165f" + integrity sha512-Z7TteWplNr0zigBee7tHd8273DG7jKpLA3YTu06WPa/nOxJsGwJDL1FeCr7Jig4kEuAkwW0dGq4P99duqlNhWg== + dependencies: + ra-core "^4.16.2" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -8204,7 +8220,7 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -ra-core@^4.16.15: +ra-core@^4.11.2, ra-core@^4.16.15, ra-core@^4.16.2: version "4.16.15" resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-4.16.15.tgz#110e7b6b701d09e0e4589ca6f5e742412fd6ee5f" integrity sha512-S4bTYXHWCbYqjHVoTw2hmYo0Ny82sLmfMXxMLcFjVDjJ7mspt7Z/96y1t96i32jeYuoRhvXeHsIE3ABndkMWVw== @@ -8228,11 +8244,6 @@ ra-i18n-polyglot@^4.16.15: node-polyglot "^2.2.2" ra-core "^4.16.15" -ra-language-chinese@^2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/ra-language-chinese/-/ra-language-chinese-2.0.10.tgz#7c51b4d13cd6cf62cf8b4e945e489ac85bdc0e7f" - integrity sha512-k+X6XdkBEZnmpKIJZj9Lb77Lj8LCmterilJTj2ovp3i8/H/dLo9IujASfjFypjHnVUpN7Y63LT19kgPrS6+row== - ra-language-english@^4.16.15: version "4.16.15" resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-4.16.15.tgz#73b9121560c5548025d823c82028631c1d7cc783" @@ -8252,11 +8263,6 @@ ra-language-french@^4.16.15: dependencies: ra-core "^4.16.15" -ra-language-german@^3.13.4: - version "3.13.5" - resolved "https://registry.yarnpkg.com/ra-language-german/-/ra-language-german-3.13.5.tgz#553385555907713a2ae08ba8f7aabd041b922936" - integrity sha512-dGWWJbIP5FUp0axoH+7sZj6011IgifEz55FLij3QSBI4ZusDdH/CL3l4oy+srUXFI+IzoXGGtQPJ7ax3p11cLQ== - ra-language-italian@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/ra-language-italian/-/ra-language-italian-3.13.1.tgz#115f15c00f140ba319d5daf53c7bd477c73b9546" @@ -9695,6 +9701,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^3.12.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" + integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" From 531d8f2d7f29789392bc2e4b05356eb8378afb9d Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 23 Apr 2024 10:37:29 +0200 Subject: [PATCH 18/33] Fix translations for registration tokens Change-Id: I31ec0a60e3aa94d55c86b1b73ef21b91f2356729 --- src/i18n/en.js | 24 ++++++++++++------------ src/i18n/fa.js | 24 ++++++++++++------------ src/i18n/fr.js | 30 +++++++++++++++--------------- src/i18n/it.js | 24 ++++++++++++------------ 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/i18n/en.js b/src/i18n/en.js index a7d234b..af5c58a 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -372,19 +372,19 @@ const en = { }, action: { reconnect: "Reconnect" }, }, - }, - registration_tokens: { - name: "Registration tokens", - fields: { - token: "Token", - valid: "Valid token", - uses_allowed: "Uses allowed", - pending: "Pending", - completed: "Completed", - expiry_time: "Expiry time", - length: "Length", + registration_tokens: { + name: "Registration tokens", + fields: { + token: "Token", + valid: "Valid token", + uses_allowed: "Uses allowed", + pending: "Pending", + completed: "Completed", + expiry_time: "Expiry time", + length: "Length", + }, + helper: { length: "Length of the token if no token is given." }, }, - helper: { length: "Length of the token if no token is given." }, }, }; export default en; diff --git a/src/i18n/fa.js b/src/i18n/fa.js index e5b7236..01ced4a 100644 --- a/src/i18n/fa.js +++ b/src/i18n/fa.js @@ -364,19 +364,19 @@ const fa = { }, action: { reconnect: "دوباره وصل شوید" }, }, - }, - registration_tokens: { - name: "توکن های ثبت نام", - fields: { - token: "توکن", - valid: "توکن معتبر", - uses_allowed: "موارد استفاده مجاز", - pending: "انتظار", - completed: "تکمیل شد", - expiry_time: "زمان انقضا", - length: "طول", + registration_tokens: { + name: "توکن های ثبت نام", + fields: { + token: "توکن", + valid: "توکن معتبر", + uses_allowed: "موارد استفاده مجاز", + pending: "انتظار", + completed: "تکمیل شد", + expiry_time: "زمان انقضا", + length: "طول", + }, + helper: { length: "طول توکن در صورت عدم ارائه توکن." }, }, - helper: { length: "طول توکن در صورت عدم ارائه توکن." }, }, }; export default fa; diff --git a/src/i18n/fr.js b/src/i18n/fr.js index 5d2df55..bc16a33 100644 --- a/src/i18n/fr.js +++ b/src/i18n/fr.js @@ -356,21 +356,21 @@ const fr = { send_failure: "Une erreur s'est produite", }, }, - }, - registration_tokens: { - name: "Jetons d'inscription", - fields: { - token: "Jeton", - valid: "Jeton valide", - uses_allowed: "Nombre d'inscription autorisées", - pending: "Nombre d'inscription en cours", - completed: "Nombre d'inscription accomplie", - expiry_time: "Date d'expiration", - length: "Longueur", - }, - helper: { - length: - "Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié", + registration_tokens: { + name: "Jetons d'inscription", + fields: { + token: "Jeton", + valid: "Jeton valide", + uses_allowed: "Nombre d'inscription autorisées", + pending: "Nombre d'inscription en cours", + completed: "Nombre d'inscription accomplie", + expiry_time: "Date d'expiration", + length: "Longueur", + }, + helper: { + length: + "Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié", + }, }, }, }; diff --git a/src/i18n/it.js b/src/i18n/it.js index 90b617b..784dfd4 100644 --- a/src/i18n/it.js +++ b/src/i18n/it.js @@ -367,19 +367,19 @@ const it = { }, 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", + 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." }, }, - helper: { length: "Lunghezza del token se non viene dato alcun token." }, }, }; export default it; From c6f9dbec183b898332f73d8edea70b8bb06ed0dc Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sat, 20 Apr 2024 10:29:44 +0300 Subject: [PATCH 19/33] Show links to media content from local users and reports (#508) Change-Id: Ia8993470a9dd5e479c028013b5be6723f569814d --- src/components/EventReports.jsx | 3 ++ src/components/media.jsx | 51 +++++++++++++++++++++++++++++++++ src/components/users.jsx | 8 ++++-- src/i18n/de.js | 6 ++++ src/i18n/en.js | 7 +++++ src/synapse/synapse.js | 5 ++++ 6 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/components/EventReports.jsx b/src/components/EventReports.jsx index 0f0810d..6ee3baf 100644 --- a/src/components/EventReports.jsx +++ b/src/components/EventReports.jsx @@ -15,6 +15,7 @@ import { useRecordContext, useTranslate, } from "react-admin"; +import { MXCField } from "./media"; import PageviewIcon from "@mui/icons-material/Pageview"; import ReportIcon from "@mui/icons-material/Warning"; import ViewListIcon from "@mui/icons-material/ViewList"; @@ -89,6 +90,8 @@ export const ReportShow = props => { + + diff --git a/src/components/media.jsx b/src/components/media.jsx index cc06a0b..1da27b9 100644 --- a/src/components/media.jsx +++ b/src/components/media.jsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import get from "lodash/get"; import { BooleanInput, Button, @@ -14,10 +15,12 @@ import { useRefresh, useTranslate, } from "react-admin"; +import { Link } from "react-router-dom"; import BlockIcon from "@mui/icons-material/Block"; import ClearIcon from "@mui/icons-material/Clear"; import DeleteSweepIcon from "@mui/icons-material/DeleteSweep"; import { + Box, Dialog, DialogContent, DialogContentText, @@ -27,7 +30,9 @@ import { import IconCancel from "@mui/icons-material/Cancel"; import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; +import FileOpenIcon from "@mui/icons-material/FileOpen"; import { alpha, useTheme } from "@mui/material/styles"; +import { getMediaUrl } from "../synapse/synapse"; const DeleteMediaDialog = ({ open, loading, onClose, onSubmit }) => { const translate = useTranslate(); @@ -333,3 +338,49 @@ export const QuarantineMediaButton = props => { ); }; + +export const ViewMediaButton = ({ media_id, label }) => { + const translate = useTranslate(); + const url = getMediaUrl(media_id); + return ( + + + + + + + {label} + + ); +}; + +export const MediaIDField = ({ source }) => { + const homeserver = localStorage.getItem("home_server"); + const record = useRecordContext(); + if (!record) return null; + + const src = get(record, source)?.toString(); + if (!src) return null; + + return ; +}; + +export const MXCField = ({ source }) => { + const record = useRecordContext(); + if (!record) return null; + + const src = get(record, source)?.toString(); + if (!src) return null; + + const media_id = src.replace("mxc://", ""); + + return ; +}; diff --git a/src/components/users.jsx b/src/components/users.jsx index f1744cc..683482d 100644 --- a/src/components/users.jsx +++ b/src/components/users.jsx @@ -51,7 +51,11 @@ import { Link } from "react-router-dom"; import AvatarField from "./AvatarField"; import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices"; import { DeviceRemoveButton } from "./devices"; -import { ProtectMediaButton, QuarantineMediaButton } from "./media"; +import { + MediaIDField, + ProtectMediaButton, + QuarantineMediaButton, +} from "./media"; const choices_medium = [ { id: "email", name: "resources.users.email" }, @@ -449,13 +453,13 @@ export const UserEdit = props => { sort={{ field: "created_ts", order: "DESC" }} > + - diff --git a/src/i18n/de.js b/src/i18n/de.js index c7d8014..bd5f2a6 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -208,6 +208,9 @@ const de = { format: "Nachrichtenformat", formatted_body: "Formatierter Nachrichteninhalt", algorithm: "Verschlüsselungsalgorithmus", + info: { + mimetype: "Typ", + }, }, }, }, @@ -256,6 +259,9 @@ const de = { created_ts: "Erstellt", last_access_ts: "Letzter Zugriff", }, + action: { + open: "Mediendatei in neuem Fenster öffnen", + }, }, delete_media: { name: "Medien", diff --git a/src/i18n/en.js b/src/i18n/en.js index af5c58a..27a6d17 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -205,6 +205,10 @@ const en = { format: "format", formatted_body: "formatted content", algorithm: "algorithm", + url: "URL", + info: { + mimetype: "Type", + }, }, }, }, @@ -253,6 +257,9 @@ const en = { created_ts: "Created", last_access_ts: "Last access", }, + action: { + open: "Open media file in new window", + }, }, delete_media: { name: "Media", diff --git a/src/synapse/synapse.js b/src/synapse/synapse.js index ddd4136..915144c 100644 --- a/src/synapse/synapse.js +++ b/src/synapse/synapse.js @@ -53,3 +53,8 @@ export const getSupportedLoginFlows = async baseUrl => { const response = await fetchUtils.fetchJson(loginFlowsUrl, { method: "GET" }); return response.json.flows; }; + +export const getMediaUrl = media_id => { + const baseUrl = localStorage.getItem("base_url"); + return `${baseUrl}/_matrix/media/v1/download/${media_id}?allow_redirect=true`; +}; From 428cb56fdf73723d3a6bf2b384050f00711876b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:50:17 +0200 Subject: [PATCH 20/33] Bump JamesIves/github-pages-deploy-action from 4.5.0 to 4.6.0 (#509) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.5.0...v4.6.0) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/edge_ghpage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/edge_ghpage.yml b/.github/workflows/edge_ghpage.yml index be91f65..1e88f67 100644 --- a/.github/workflows/edge_ghpage.yml +++ b/.github/workflows/edge_ghpage.yml @@ -23,7 +23,7 @@ jobs: yarn build - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@v4.6.0 with: branch: gh-pages folder: build From ef3836313c69957b32713c68d96b808bd62dd4f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:50:40 +0200 Subject: [PATCH 21/33] Bump softprops/action-gh-release from 2.0.3 to 2.0.4 (#510) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/3198ee18f814cdf787321b4a32a26ddbf37acc52...9d7c94cfd0a1f3ed45544c887983e9fa900f0564) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/github-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index a5ece09..7f158b3 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -24,7 +24,7 @@ jobs: mkdir -p dist cp -r build synapse-admin-$version tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version - - uses: softprops/action-gh-release@3198ee18f814cdf787321b4a32a26ddbf37acc52 + - uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 with: files: dist/*.tar.gz env: From 4b1277f653ae983f8428f2241700bfbdace2032a Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Mon, 5 Feb 2024 17:32:32 +0100 Subject: [PATCH 22/33] Rework configuration process Dynamically loads `config.json` on startup. Fixes #167, #284, #449, #486 Change-Id: I9efb1079c0c88e6e0272c5fda734a367aa8f84a3 --- .env | 5 -- Dockerfile | 3 +- README.md | 42 +++++++++--- docker-compose.yml | 7 +- public/config.json | 1 + public/index.html | 104 +++++++++++++++++++++++++++++- src/AppContext.jsx | 5 ++ src/components/LoginPage.jsx | 49 ++++++++++---- src/components/LoginPage.test.jsx | 61 +++++++++++++++++- src/index.jsx | 20 ++++-- src/synapse/authProvider.js | 3 - 11 files changed, 254 insertions(+), 46 deletions(-) delete mode 100644 .env create mode 100644 public/config.json create mode 100644 src/AppContext.jsx diff --git a/.env b/.env deleted file mode 100644 index 7bc3e94..0000000 --- a/.env +++ /dev/null @@ -1,5 +0,0 @@ -# This setting allows to fix the homeserver. -# If you set this setting, the user will not be able to select -# the server and have to use synapse-admin with this server. - -#REACT_APP_SERVER=https://yourmatrixserver.example.com \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f936a8c..4e76929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,12 @@ # Builder FROM node:lts as builder LABEL org.opencontainers.image.url=https://github.com/Awesome-Technologies/synapse-admin org.opencontainers.image.source=https://github.com/Awesome-Technologies/synapse-admin -ARG REACT_APP_SERVER WORKDIR /src COPY . /src RUN yarn --network-timeout=300000 install --immutable -RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build +RUN yarn build # App diff --git a/README.md b/README.md index 89211de..751f7d0 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,6 @@ You have three options: - download dependencies: `yarn install` - start web server: `yarn start` -You can fix the homeserver, so that the user can no longer define it himself. -Either you define it at startup (e.g. `REACT_APP_SERVER=https://yourmatrixserver.example.com yarn start`) -or by editing it in the [.env](.env) file. See also the -[documentation](https://create-react-app.dev/docs/adding-custom-environment-variables/). - #### Steps for 3) - run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the [docker-compose.yml](docker-compose.yml): `docker-compose up -d` @@ -76,8 +71,6 @@ or by editing it in the [.env](.env) file. See also the > note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail. ```yml - version: "3" - services: synapse-admin: container_name: synapse-admin @@ -88,7 +81,6 @@ or by editing it in the [.env](.env) file. See also the # - NODE_OPTIONS="--max_old_space_size=1024" # # see #266, PUBLIC_URL must be without surrounding quotation marks # - PUBLIC_URL=/synapse-admin - # - REACT_APP_SERVER="https://matrix.example.com" ports: - "8080:80" restart: unless-stopped @@ -96,6 +88,40 @@ or by editing it in the [.env](.env) file. See also the - browse to http://localhost:8080 +### Restricting available homeserver + +You can restrict the homeserver(s), so that the user can no longer define it himself. + +Edit `config.json` to restrict either to a single homeserver: + +```json +{ + "restrictBaseUrl": "https://your-matrixs-erver.example.com" +} +``` + +or to a list of homeservers: + +```json +{ + "restrictBaseUrl": [ + "https://your-first-matrix-server.example.com", + "https://your-second-matrix-server.example.com" + ] +} +``` + +The `config.json` can be injected into a Docker container using a bind mount. + +```yml +services: + synapse-admin: + ... + volumes: + ./config.json:/app/config.json + ... +``` + ## Screenshots ![Screenshots](./screenshots.jpg) diff --git a/docker-compose.yml b/docker-compose.yml index 6e07aa2..6b16162 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,10 @@ -version: "3" - services: synapse-admin: container_name: synapse-admin hostname: synapse-admin image: awesometechnologies/synapse-admin:latest # build: - # context: . + # context: . # to use the docker-compose as standalone without a local repo clone, # replace the context definition with this: @@ -18,9 +16,6 @@ services: # - NODE_OPTIONS="--max_old_space_size=1024" # default is . # - PUBLIC_URL=/synapse-admin - # You can use a fixed homeserver, so that the user can no longer - # define it himself - # - REACT_APP_SERVER="https://matrix.example.com" ports: - "8080:80" restart: unless-stopped diff --git a/public/config.json b/public/config.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/public/config.json @@ -0,0 +1 @@ +{} diff --git a/public/index.html b/public/index.html index 2b90907..3498b4c 100644 --- a/public/index.html +++ b/public/index.html @@ -24,10 +24,110 @@ Learn how to configure a non-root public URL by running `npm run build`. --> Synapse-Admin + -
+
+
+
Loading...
+
+
- - + + Synapse-Admin