Compare commits
38 Commits
v0.10.3-et
...
v0.10.3-et
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8680dbc268 | ||
|
|
ea2b84c5dc | ||
|
|
45c7027d3c | ||
|
|
62017d4f4e | ||
|
|
0d7dcdc284 | ||
|
|
0eb3b77bc5 | ||
|
|
e2fba4bbdd | ||
|
|
6425a6bfc4 | ||
|
|
42925e8a7c | ||
|
|
75e89fe628 | ||
|
|
f9c806d292 | ||
|
|
3f5022d515 | ||
|
|
0748f98d47 | ||
|
|
3c8fd351a1 | ||
|
|
40e6d80c35 | ||
|
|
243cc40da4 | ||
|
|
3bcc51d12c | ||
|
|
2afd7d6737 | ||
|
|
2357d63120 | ||
|
|
e28d07ebd3 | ||
|
|
33f960579c | ||
|
|
6e14bd7959 | ||
|
|
bdbc0df95b | ||
|
|
5e10d94e5f | ||
|
|
a934942bf6 | ||
|
|
c440e88806 | ||
|
|
45b7ec005b | ||
|
|
c748523dbc | ||
|
|
34eea8dff4 | ||
|
|
87408c0e6d | ||
|
|
5ad787075c | ||
|
|
01ae5a411f | ||
|
|
cde60a2aba | ||
|
|
3f5808c67b | ||
|
|
2c697b40dd | ||
|
|
9453490bca | ||
|
|
0baf6ad94d | ||
|
|
df911c9e97 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/testdata
|
||||||
11
Dockerfile.build
Normal file
11
Dockerfile.build
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM node:lts AS builder
|
||||||
|
ARG BASE_PATH=./
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . /src
|
||||||
|
RUN yarn config set enableTelemetry 0 && \
|
||||||
|
yarn install --immutable --network-timeout=300000 && \
|
||||||
|
yarn build --base=$BASE_PATH
|
||||||
|
|
||||||
|
FROM ghcr.io/static-web-server/static-web-server:2
|
||||||
|
ENV SERVER_ROOT=/app
|
||||||
|
COPY --from=builder /src/dist /app
|
||||||
14
README.md
14
README.md
@@ -261,6 +261,7 @@ You have three options:
|
|||||||
hostname: synapse-admin
|
hostname: synapse-admin
|
||||||
build:
|
build:
|
||||||
context: https://github.com/etkecc/synapse-admin.git
|
context: https://github.com/etkecc/synapse-admin.git
|
||||||
|
dockerfile: Dockerfile.build
|
||||||
args:
|
args:
|
||||||
- BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
- BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||||
@@ -289,7 +290,7 @@ Example for Traefik:
|
|||||||
```yml
|
```yml
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:mimolette
|
image: traefik:v3
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
@@ -302,11 +303,12 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.synapse-admin.rule=Host(`example.com`)&&PathPrefix(`/admin`)"
|
- "traefik.http.routers.admin.rule=Host(`example.com`) && PathPrefix(`/admin`)"
|
||||||
- "traefik.http.routers.synapse-admin.middlewares=admin,admin_path"
|
- "traefik.http.services.admin.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.admin.redirectregex.regex=^(.*)/admin/?"
|
- "traefik.http.middlewares.admin-slashless-redirect.redirectregex.regex=(/admin)$$"
|
||||||
- "traefik.http.middlewares.admin.redirectregex.replacement=$${1}/admin/"
|
- "traefik.http.middlewares.admin-slashless-redirect.redirectregex.replacement=$${1}/"
|
||||||
- "traefik.http.middlewares.admin_path.stripprefix.prefixes=/admin"
|
- "traefik.http.middlewares.admin-strip-prefix.stripprefix.prefixes=/admin"
|
||||||
|
- "traefik.http.routers.admin.middlewares=admin-slashless-redirect,admin-strip-prefix"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ services:
|
|||||||
image: ghcr.io/etkecc/synapse-admin:latest
|
image: ghcr.io/etkecc/synapse-admin:latest
|
||||||
# build:
|
# build:
|
||||||
# context: .
|
# context: .
|
||||||
|
# dockerfile: Dockerfile.build
|
||||||
|
|
||||||
# to use the docker-compose as standalone without a local repo clone,
|
# to use the docker-compose as standalone without a local repo clone,
|
||||||
# replace the context definition with this:
|
# replace the context definition with this:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { JestConfigWithTsJest } from "ts-jest";
|
|||||||
|
|
||||||
const config: JestConfigWithTsJest = {
|
const config: JestConfigWithTsJest = {
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
testEnvironment: "jsdom",
|
testEnvironment: "jest-fixed-jsdom",
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
coveragePathIgnorePatterns: ["node_modules", "dist"],
|
coveragePathIgnorePatterns: ["node_modules", "dist"],
|
||||||
coverageDirectory: "<rootDir>/coverage/",
|
coverageDirectory: "<rootDir>/coverage/",
|
||||||
|
|||||||
41
package.json
41
package.json
@@ -11,24 +11,24 @@
|
|||||||
"url": "https://github.com/etkecc/synapse-admin"
|
"url": "https://github.com/etkecc/synapse-admin"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@testing-library/dom": "^10.0.0",
|
"@testing-library/dom": "^10.0.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/lodash": "^4.17.14",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.10",
|
||||||
"@types/papaparse": "^5.3.15",
|
"@types/papaparse": "^5.3.15",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||||
"@typescript-eslint/parser": "^8.19.0",
|
"@typescript-eslint/parser": "^8.19.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"eslint-plugin-yaml": "^1.0.3",
|
"eslint-plugin-yaml": "^1.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
"react-test-renderer": "^18.3.1",
|
"react-test-renderer": "^18.3.1",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.19.0",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^6.0.7",
|
"vite": "^6.0.11",
|
||||||
"vite-plugin-version-mark": "^0.1.4"
|
"vite-plugin-version-mark": "^0.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -49,26 +49,25 @@
|
|||||||
"@haleos/ra-language-german": "^1.0.0",
|
"@haleos/ra-language-german": "^1.0.0",
|
||||||
"@haxqer/ra-language-chinese": "^4.16.2",
|
"@haxqer/ra-language-chinese": "^4.16.2",
|
||||||
"@mui/icons-material": "^6.3.1",
|
"@mui/icons-material": "^6.3.1",
|
||||||
"@mui/material": "^6.3.1",
|
"@mui/material": "^6.4.0",
|
||||||
"@mui/utils": "^5.16.13",
|
"@mui/utils": "^5.16.14",
|
||||||
"@tanstack/react-query": "^5.62.15",
|
"@tanstack/react-query": "^5.64.2",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
"jest-fixed-jsdom": "^0.0.9",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.5.1",
|
||||||
"ra-core": "^5.4.1",
|
"ra-core": "^5.4.4",
|
||||||
"ra-i18n-polyglot": "^5.3.4",
|
"ra-i18n-polyglot": "^5.4.4",
|
||||||
"ra-language-english": "^5.3.4",
|
"ra-language-english": "^5.4.4",
|
||||||
"ra-language-farsi": "^5.1.0",
|
"ra-language-farsi": "^5.1.0",
|
||||||
"ra-language-french": "^5.4.3",
|
"ra-language-french": "^5.5.2",
|
||||||
"ra-language-italian": "^3.13.1",
|
"ra-language-italian": "^3.13.1",
|
||||||
"ra-language-russian": "^4.14.2",
|
"ra-language-russian": "^4.14.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-admin": "^5.4.3",
|
"react-admin": "^5.5.2",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-is": "^18.3.1",
|
"react-is": "^18.3.1",
|
||||||
"react-router": "^6.28.1",
|
|
||||||
"react-router-dom": "^6.28.1",
|
|
||||||
"ts-jest-mock-import-meta": "^1.2.1"
|
"ts-jest-mock-import-meta": "^1.2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import fetchMock from "jest-fetch-mock";
|
import fetchMock from "jest-fetch-mock";
|
||||||
fetchMock.enableMocks();
|
fetchMock.enableMocks();
|
||||||
|
|
||||||
|
jest.mock("./synapse/authProvider", () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
logout: jest.fn().mockResolvedValue(undefined),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset all mocks before each test
|
||||||
|
fetchMock.resetMocks();
|
||||||
|
// Mock any fetch call to return empty JSON immediately
|
||||||
|
fetchMock.mockResponseOnce(JSON.stringify({}));
|
||||||
|
});
|
||||||
|
|
||||||
it("renders", async () => {
|
it("renders", async () => {
|
||||||
render(<App />);
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
await screen.findAllByText("Welcome to Synapse Admin");
|
await screen.findAllByText("Welcome to Synapse Admin");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { dateParser } from "../utils/date";
|
import { dateParser } from "../utils/date";
|
||||||
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
|
import { DeleteMediaParams, SynapseDataProvider } from "../synapse/dataProvider";
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
|
import decodeURLComponent from "../utils/decodeURLComponent";
|
||||||
|
|
||||||
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
@@ -481,7 +482,7 @@ export const MediaIDField = ({ source }) => {
|
|||||||
|
|
||||||
let uploadName = mediaID;
|
let uploadName = mediaID;
|
||||||
if (get(record, "upload_name")) {
|
if (get(record, "upload_name")) {
|
||||||
uploadName = decodeURIComponent(get(record, "upload_name")?.toString());
|
uploadName = decodeURLComponent(get(record, "upload_name")?.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mxcURL = mediaID;
|
let mxcURL = mediaID;
|
||||||
@@ -504,7 +505,10 @@ export const ReportMediaContent = ({ source }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadName = decodeURIComponent(get(record, "event_json.content.body")?.toString());
|
let uploadName = "";
|
||||||
|
if (get(record, "event_json.content.body")) {
|
||||||
|
uploadName = decodeURLComponent(get(record, "event_json.content.body")?.toString());
|
||||||
|
}
|
||||||
|
|
||||||
return <ViewMediaButton mxcURL={mxcURL} label={mxcURL} uploadName={uploadName} mimetype={record.media_type}/>;
|
return <ViewMediaButton mxcURL={mxcURL} label={mxcURL} uploadName={uploadName} mimetype={record.media_type}/>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const fixedGermanMessages = {
|
|||||||
action: {
|
action: {
|
||||||
...formalGermanMessages.ra.action,
|
...formalGermanMessages.ra.action,
|
||||||
update_application: "Anwendung aktualisieren",
|
update_application: "Anwendung aktualisieren",
|
||||||
|
select_all_button: "Alle auswählen",
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
...formalGermanMessages.ra.page,
|
...formalGermanMessages.ra.page,
|
||||||
@@ -28,6 +29,7 @@ const fixedGermanMessages = {
|
|||||||
"Sie haben nicht die erforderlichen Berechtigungen um auf diese Seite zuzugreifen.",
|
"Sie haben nicht die erforderlichen Berechtigungen um auf diese Seite zuzugreifen.",
|
||||||
authentication_error:
|
authentication_error:
|
||||||
"Der Authentifizierungsserver hat einen Fehler zurückgegeben und Ihre Anmeldedaten konnten nicht überprüft werden.",
|
"Der Authentifizierungsserver hat einen Fehler zurückgegeben und Ihre Anmeldedaten konnten nicht überprüft werden.",
|
||||||
|
select_all_limit_reached: "Es gibt zu viele Elemente, um sie alle auszuwählen. Es wurden nur die ersten %{max} Elemente ausgewählt.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const fixedRussianMessages = {
|
|||||||
no_filtered_results: "Нет результатов",
|
no_filtered_results: "Нет результатов",
|
||||||
clear_filters: "Все фильтры сбросить",
|
clear_filters: "Все фильтры сбросить",
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
...russianMessages.ra.action,
|
||||||
|
select_all_button: "Выбрать все",
|
||||||
|
},
|
||||||
page: {
|
page: {
|
||||||
...russianMessages.ra.page,
|
...russianMessages.ra.page,
|
||||||
empty: "Пусто",
|
empty: "Пусто",
|
||||||
@@ -23,6 +27,7 @@ const fixedRussianMessages = {
|
|||||||
"У вас нет прав доступа к этой странице.",
|
"У вас нет прав доступа к этой странице.",
|
||||||
authentication_error:
|
authentication_error:
|
||||||
"Сервер аутентификации вернул ошибку и не смог проверить ваши учетные данные.",
|
"Сервер аутентификации вернул ошибку и не смог проверить ваши учетные данные.",
|
||||||
|
select_all_limit_reached: "Слишком много элементов для выбора. Были выбраны только первые %{max} элементов.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ const fixedChineseMessages = {
|
|||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
...chineseMessages.ra.action,
|
...chineseMessages.ra.action,
|
||||||
update_application: "Anwendung aktualisieren",
|
update_application: "更新应用",
|
||||||
|
select_all_button: "全部选择",
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
...chineseMessages.ra.page,
|
...chineseMessages.ra.page,
|
||||||
@@ -26,6 +27,7 @@ const fixedChineseMessages = {
|
|||||||
"您没有访问此页面的权限。",
|
"您没有访问此页面的权限。",
|
||||||
authentication_error:
|
authentication_error:
|
||||||
"身份验证服务器返回错误,无法验证您的凭据。",
|
"身份验证服务器返回错误,无法验证您的凭据。",
|
||||||
|
select_all_limit_reached: "选择的元素太多。只选择了前 %{max} 个元素。",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import polyglotI18nProvider from "ra-i18n-polyglot";
|
|||||||
|
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { AdminContext } from "react-admin";
|
import { AdminContext } from "react-admin";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
import { AppContext } from "../Context";
|
import { AppContext } from "../Context";
|
||||||
@@ -14,9 +15,11 @@ describe("LoginForm", () => {
|
|||||||
it("renders with no restriction to homeserver", async () => {
|
it("renders with no restriction to homeserver", async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
<AdminContext i18nProvider={i18nProvider}>
|
<AdminContext i18nProvider={i18nProvider}>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</AdminContext>
|
</AdminContext>
|
||||||
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,6 +36,7 @@ describe("LoginForm", () => {
|
|||||||
|
|
||||||
it("renders with single restricted homeserver", () => {
|
it("renders with single restricted homeserver", () => {
|
||||||
render(
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
<AppContext.Provider
|
<AppContext.Provider
|
||||||
value={{ restrictBaseUrl: "https://matrix.example.com", asManagedUsers: [], menu: [] }}
|
value={{ restrictBaseUrl: "https://matrix.example.com", asManagedUsers: [], menu: [] }}
|
||||||
>
|
>
|
||||||
@@ -40,6 +44,7 @@ describe("LoginForm", () => {
|
|||||||
<LoginPage />
|
<LoginPage />
|
||||||
</AdminContext>
|
</AdminContext>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
screen.getByText(englishMessages.synapseadmin.auth.welcome);
|
screen.getByText(englishMessages.synapseadmin.auth.welcome);
|
||||||
@@ -63,7 +68,9 @@ describe("LoginForm", () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AdminContext i18nProvider={i18nProvider}>
|
<AdminContext i18nProvider={i18nProvider}>
|
||||||
|
<BrowserRouter>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
|
</BrowserRouter>
|
||||||
</AdminContext>
|
</AdminContext>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ import UserRateLimits from "../components/UserRateLimits";
|
|||||||
import { User, UsernameAvailabilityResult } from "../synapse/dataProvider";
|
import { User, UsernameAvailabilityResult } from "../synapse/dataProvider";
|
||||||
import { MakeAdminBtn } from "./rooms";
|
import { MakeAdminBtn } from "./rooms";
|
||||||
import UserAccountData from "../components/UserAccountData";
|
import UserAccountData from "../components/UserAccountData";
|
||||||
|
import decodeURLComponent from "../utils/decodeURLComponent";
|
||||||
|
|
||||||
const choices_medium = [
|
const choices_medium = [
|
||||||
{ id: "email", name: "resources.users.email" },
|
{ id: "email", name: "resources.users.email" },
|
||||||
@@ -505,7 +506,7 @@ export const UserEdit = (props: EditProps) => {
|
|||||||
<DateField source="last_access_ts" showTime options={DATE_FORMAT} />
|
<DateField source="last_access_ts" showTime options={DATE_FORMAT} />
|
||||||
<NumberField source="media_length" />
|
<NumberField source="media_length" />
|
||||||
<TextField source="media_type" sx={{ display: "block", width: 200, wordBreak: "break-word" }} />
|
<TextField source="media_type" sx={{ display: "block", width: 200, wordBreak: "break-word" }} />
|
||||||
<FunctionField source="upload_name" render={record => decodeURIComponent(record.upload_name)} />
|
<FunctionField source="upload_name" render={record => record.upload_name ? decodeURLComponent(record.upload_name) : ""} />
|
||||||
<TextField source="quarantined_by" />
|
<TextField source="quarantined_by" />
|
||||||
<QuarantineMediaButton label="resources.quarantine_media.action.name" />
|
<QuarantineMediaButton label="resources.quarantine_media.action.name" />
|
||||||
<ProtectMediaButton label="resources.users_media.fields.safe_from_quarantine" />
|
<ProtectMediaButton label="resources.users_media.fields.safe_from_quarantine" />
|
||||||
@@ -527,7 +528,9 @@ export const UserEdit = (props: EditProps) => {
|
|||||||
<ReferenceField reference="rooms" source="id" label="resources.rooms.fields.joined_members" link={false} sortable={false}>
|
<ReferenceField reference="rooms" source="id" label="resources.rooms.fields.joined_members" link={false} sortable={false}>
|
||||||
<TextField source="joined_members" sortable={false} />
|
<TextField source="joined_members" sortable={false} />
|
||||||
</ReferenceField>
|
</ReferenceField>
|
||||||
|
<ReferenceField reference="rooms" source="id" label={false} link={false} sortable={false}>
|
||||||
<MakeAdminBtn />
|
<MakeAdminBtn />
|
||||||
|
</ReferenceField>
|
||||||
</Datagrid>
|
</Datagrid>
|
||||||
</ReferenceManyField>
|
</ReferenceManyField>
|
||||||
</FormTab>
|
</FormTab>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AuthProvider, HttpError, Options, fetchUtils } from "react-admin";
|
|||||||
import { MatrixError, displayError } from "../utils/error";
|
import { MatrixError, displayError } from "../utils/error";
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
import { FetchConfig, ClearConfig } from "../utils/config";
|
import { FetchConfig, ClearConfig } from "../utils/config";
|
||||||
|
import decodeURLComponent from "../utils/decodeURLComponent";
|
||||||
|
|
||||||
const authProvider: AuthProvider = {
|
const authProvider: AuthProvider = {
|
||||||
// called when the user attempts to log in
|
// called when the user attempts to log in
|
||||||
@@ -57,7 +58,7 @@ const authProvider: AuthProvider = {
|
|||||||
base_url = base_url.replace(/\/+$/g, "");
|
base_url = base_url.replace(/\/+$/g, "");
|
||||||
localStorage.setItem("base_url", base_url);
|
localStorage.setItem("base_url", base_url);
|
||||||
|
|
||||||
const decoded_base_url = window.decodeURIComponent(base_url);
|
const decoded_base_url = decodeURLComponent(base_url);
|
||||||
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login");
|
let login_api_url = decoded_base_url + (accessToken ? "/_matrix/client/v3/account/whoami" : "/_matrix/client/v3/login");
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
|||||||
15
src/utils/decodeURLComponent.ts
Normal file
15
src/utils/decodeURLComponent.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Decode a URI component, and if it fails, return the original string.
|
||||||
|
* @param str The string to decode.
|
||||||
|
* @returns The decoded string, or the original string if decoding fails.
|
||||||
|
* @example decodeURIComponent("Hello%20World") // "Hello World"
|
||||||
|
*/
|
||||||
|
const decodeURLComponent = (str: any): any => {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(str);
|
||||||
|
} catch (e) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default decodeURLComponent;
|
||||||
Reference in New Issue
Block a user