mirror of
https://github.com/Awesome-Technologies/synapse-admin.git
synced 2025-08-16 23:48:16 +02:00
Compare commits
25 Commits
TIM/0.10.3
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
e15411a04c | ||
|
3902dcd3d1 | ||
|
ef8ae9b38f | ||
|
7a286ad506 | ||
|
fa3f2437a3 | ||
|
8dc5238fcb | ||
|
238350b940 | ||
|
99bf7b1889 | ||
|
d72c91644d | ||
|
e8e28b5df1 | ||
|
d5c10b6e02 | ||
|
3085b9ffa0 | ||
|
b2a3fb0f87 | ||
|
1e8b4cc885 | ||
|
4d1a9cc147 | ||
|
1b8b702270 | ||
|
61c32fb473 | ||
|
ad876bb790 | ||
|
2524848dae | ||
|
669c1f3079 | ||
|
590f673167 | ||
|
307793f000 | ||
|
96f549fe42 | ||
|
3de4332477 | ||
|
9fc005032c |
3
.github/workflows/build-test.yml
vendored
3
.github/workflows/build-test.yml
vendored
@ -5,6 +5,9 @@ on:
|
||||
branches: ["master"]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
4
.github/workflows/edge_ghpage.yml
vendored
4
.github/workflows/edge_ghpage.yml
vendored
@ -5,6 +5,8 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
@ -23,7 +25,7 @@ jobs:
|
||||
yarn build --base=/synapse-admin
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.6.3
|
||||
uses: JamesIves/github-pages-deploy-action@v4.7.3
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: dist
|
||||
|
2
.github/workflows/github-release.yml
vendored
2
.github/workflows/github-release.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
version=`git describe --dirty --tags || echo unknown`
|
||||
cp -r dist synapse-admin-$version
|
||||
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
|
||||
- uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
|
||||
- uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
with:
|
||||
files: dist/*.tar.gz
|
||||
env:
|
||||
|
893
.yarn/releases/yarn-4.1.1.cjs
vendored
893
.yarn/releases/yarn-4.1.1.cjs
vendored
File diff suppressed because one or more lines are too long
925
.yarn/releases/yarn-4.4.1.cjs
vendored
Executable file
925
.yarn/releases/yarn-4.4.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.4.1.cjs
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Builder
|
||||
FROM node:lts as 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
|
||||
# Base path for synapse admin
|
||||
ARG BASE_PATH=./
|
||||
|
@ -2,7 +2,7 @@ import type { JestConfigWithTsJest } from "ts-jest";
|
||||
|
||||
const config: JestConfigWithTsJest = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
testEnvironment: "jest-fixed-jsdom",
|
||||
collectCoverage: true,
|
||||
coveragePathIgnorePatterns: ["node_modules", "dist"],
|
||||
coverageDirectory: "<rootDir>/coverage/",
|
||||
|
41
package.json
41
package.json
@ -10,10 +10,12 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Awesome-Technologies/synapse-admin"
|
||||
},
|
||||
"packageManager": "yarn@4.1.1",
|
||||
"packageManager": "yarn@4.4.1",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.7.0",
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@mui/system": "^7.1.0",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
@ -30,46 +32,47 @@
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-yaml": "^1.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-fixed-jsdom": "^0.0.9",
|
||||
"prettier": "^3.3.3",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.2.3",
|
||||
"ts-jest": "^29.3.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.16.1",
|
||||
"vite": "^5.3.4",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-version-mark": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@haleos/ra-language-german": "^1.0.0",
|
||||
"@haxqer/ra-language-chinese": "^4.16.2",
|
||||
"@mui/icons-material": "^5.16.4",
|
||||
"@mui/material": "^5.16.4",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@tanstack/react-query": "^5.59.12",
|
||||
"history": "^5.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"papaparse": "^5.4.1",
|
||||
"query-string": "^7.1.3",
|
||||
"ra-core": "^4.16.20",
|
||||
"ra-i18n-polyglot": "^4.16.20",
|
||||
"ra-language-english": "^4.16.20",
|
||||
"ra-language-farsi": "^4.2.0",
|
||||
"ra-language-french": "^4.16.20",
|
||||
"ra-core": "^5.8.3",
|
||||
"ra-i18n-polyglot": "^5.8.3",
|
||||
"ra-language-english": "^5.8.3",
|
||||
"ra-language-farsi": "^5.0.0",
|
||||
"ra-language-french": "^5.8.3",
|
||||
"ra-language-italian": "^3.13.1",
|
||||
"ra-language-russian": "^4.14.2",
|
||||
"react": "^18.3.1",
|
||||
"react-admin": "^4.16.20",
|
||||
"react-admin": "^5.8.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-is": "^18.3.1",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router": "^6.25.1",
|
||||
"react-router-dom": "^6.25.1"
|
||||
"react-router": "^7.6.1",
|
||||
"react-router-dom": "^7.6.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite serve",
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import fetchMock from "jest-fetch-mock";
|
||||
fetchMock.enableMocks();
|
||||
|
||||
import App from "./App";
|
||||
|
||||
|
@ -21,6 +21,7 @@ import userMediaStats from "./resources/user_media_statistics";
|
||||
import users from "./resources/users";
|
||||
import authProvider from "./synapse/authProvider";
|
||||
import dataProvider from "./synapse/dataProvider";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
// TODO: Can we use lazy loading together with browser locale?
|
||||
const messages = {
|
||||
@ -45,7 +46,10 @@ const i18nProvider = polyglotI18nProvider(
|
||||
]
|
||||
);
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Admin
|
||||
disableTelemetry
|
||||
requireAuth
|
||||
@ -53,7 +57,6 @@ const App = () => (
|
||||
authProvider={authProvider}
|
||||
dataProvider={dataProvider}
|
||||
i18nProvider={i18nProvider}
|
||||
darkTheme={{ palette: { mode: "dark" } }}
|
||||
>
|
||||
<CustomRoutes>
|
||||
<Route path="/import_users" element={<ImportFeature />} />
|
||||
@ -76,6 +79,7 @@ const App = () => (
|
||||
<Resource name="room_state" />
|
||||
<Resource name="destination_rooms" />
|
||||
</Admin>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -121,7 +121,7 @@ const FilePicker = () => {
|
||||
|
||||
const verifyCsv = ({ data, meta, errors }: ParseResult<ImportLine>, { setValues, setStats, setError }) => {
|
||||
/* First, verify the presence of required fields */
|
||||
const missingFields = expectedFields.filter(eF => meta.fields?.find(mF => eF === mF));
|
||||
const missingFields = expectedFields.filter(eF => !meta.fields?.includes(eF));
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
setError(translate("import_users.error.required_field", { field: missingFields[0] }));
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
useTranslate,
|
||||
useUnselectAll,
|
||||
} from "react-admin";
|
||||
import { useMutation } from "react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
|
||||
const translate = useTranslate();
|
||||
@ -43,7 +43,6 @@ const ServerNoticeDialog = ({ open, onClose, onSubmit }) => {
|
||||
<TextInput
|
||||
source="body"
|
||||
label="resources.servernotices.fields.body"
|
||||
fullWidth
|
||||
multiline
|
||||
rows="4"
|
||||
resettable
|
||||
@ -64,6 +63,10 @@ export const ServerNoticeButton = () => {
|
||||
const handleDialogOpen = () => setOpen(true);
|
||||
const handleDialogClose = () => setOpen(false);
|
||||
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleSend = (values: Partial<RaRecord>) => {
|
||||
create(
|
||||
"servernotices",
|
||||
@ -100,13 +103,12 @@ export const ServerNoticeBulkButton = () => {
|
||||
const unselectAllUsers = useUnselectAll("users");
|
||||
const dataProvider = useDataProvider();
|
||||
|
||||
const { mutate: sendNotices, isLoading } = useMutation(
|
||||
data =>
|
||||
const { mutate: sendNotices, isPending } = useMutation({
|
||||
mutationFn: (data) =>
|
||||
dataProvider.createMany("servernotices", {
|
||||
ids: selectedIds,
|
||||
data: data,
|
||||
}),
|
||||
{
|
||||
onSuccess: () => {
|
||||
notify("resources.servernotices.action.send_success");
|
||||
unselectAllUsers();
|
||||
@ -116,12 +118,11 @@ export const ServerNoticeBulkButton = () => {
|
||||
notify("resources.servernotices.action.send_failure", {
|
||||
type: "error",
|
||||
}),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button label="resources.servernotices.send" onClick={openDialog} disabled={isLoading}>
|
||||
<Button label="resources.servernotices.send" onClick={openDialog} disabled={isPending}>
|
||||
<MessageIcon />
|
||||
</Button>
|
||||
<ServerNoticeDialog open={open} onClose={closeDialog} onSubmit={sendNotices} />
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
useRefresh,
|
||||
useTranslate,
|
||||
} from "react-admin";
|
||||
import { useMutation } from "react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { dateParser } from "./date";
|
||||
@ -55,14 +55,12 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
||||
<DialogContentText>{translate("delete_media.helper.send")}</DialogContentText>
|
||||
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
|
||||
<DateTimeInput
|
||||
fullWidth
|
||||
source="before_ts"
|
||||
label="delete_media.fields.before_ts"
|
||||
defaultValue={0}
|
||||
parse={dateParser}
|
||||
/>
|
||||
<NumberInput
|
||||
fullWidth
|
||||
source="size_gt"
|
||||
label="delete_media.fields.size_gt"
|
||||
defaultValue={0}
|
||||
@ -70,7 +68,6 @@ const DeleteMediaDialog = ({ open, onClose, onSubmit }) => {
|
||||
step={1024}
|
||||
/>
|
||||
<BooleanInput
|
||||
fullWidth
|
||||
source="keep_profiles"
|
||||
label="delete_media.fields.keep_profiles"
|
||||
defaultValue={true}
|
||||
@ -86,9 +83,8 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const notify = useNotify();
|
||||
const dataProvider = useDataProvider<SynapseDataProvider>();
|
||||
const { mutate: deleteMedia, isLoading } = useMutation(
|
||||
(values: DeleteMediaParams) => dataProvider.deleteMedia(values),
|
||||
{
|
||||
const { mutate: deleteMedia, isPending } = useMutation({
|
||||
mutationFn: (values: DeleteMediaParams) => dataProvider.deleteMedia(values),
|
||||
onSuccess: () => {
|
||||
notify("delete_media.action.send_success");
|
||||
closeDialog();
|
||||
@ -98,8 +94,7 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
||||
type: "error",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const openDialog = () => setOpen(true);
|
||||
const closeDialog = () => setOpen(false);
|
||||
@ -110,7 +105,7 @@ export const DeleteMediaButton = (props: ButtonProps) => {
|
||||
{...props}
|
||||
label="delete_media.action.send"
|
||||
onClick={openDialog}
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
sx={{
|
||||
color: theme.palette.error.main,
|
||||
"&:hover": {
|
||||
|
@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
||||
|
||||
const de: SynapseTranslationMessages = {
|
||||
...formalGermanMessages,
|
||||
ra: {
|
||||
...formalGermanMessages.ra,
|
||||
navigation: {
|
||||
...formalGermanMessages.ra.navigation,
|
||||
no_filtered_results: "Keine Ergebnisse",
|
||||
clear_filters: "Alle Filter entfernen",
|
||||
},
|
||||
},
|
||||
synapseadmin: {
|
||||
auth: {
|
||||
base_url: "Heimserver URL",
|
||||
|
@ -124,6 +124,7 @@ const en: SynapseTranslationMessages = {
|
||||
erased: "Erased",
|
||||
guests: "Show guests",
|
||||
show_deactivated: "Show deactivated users",
|
||||
show_locked: "Show locked users",
|
||||
user_id: "Search user",
|
||||
displayname: "Displayname",
|
||||
password: "Password",
|
||||
|
1
src/i18n/index.d.ts
vendored
1
src/i18n/index.d.ts
vendored
@ -120,6 +120,7 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
||||
erased?: string; // TODO: fa, fr, it, zh
|
||||
guests: string;
|
||||
show_deactivated: string;
|
||||
show_locked?: string; // TODO: de, fa, fr, it, zh
|
||||
user_id: string;
|
||||
displayname: string;
|
||||
password: string;
|
||||
|
@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
||||
|
||||
const ru: SynapseTranslationMessages = {
|
||||
...russianMessages,
|
||||
ra: {
|
||||
...russianMessages.ra,
|
||||
navigation: {
|
||||
...russianMessages.ra.navigation,
|
||||
no_filtered_results: "Нет результатов",
|
||||
clear_filters: "Все фильтры сбросить",
|
||||
},
|
||||
},
|
||||
synapseadmin: {
|
||||
auth: {
|
||||
base_url: "Адрес домашнего сервера",
|
||||
|
@ -4,6 +4,14 @@ import { SynapseTranslationMessages } from ".";
|
||||
|
||||
const zh: SynapseTranslationMessages = {
|
||||
...chineseMessages,
|
||||
ra: {
|
||||
...chineseMessages.ra,
|
||||
navigation: {
|
||||
...chineseMessages.ra.navigation,
|
||||
no_filtered_results: "没有结果",
|
||||
clear_filters: "清除所有过滤器",
|
||||
},
|
||||
},
|
||||
synapseadmin: {
|
||||
auth: {
|
||||
base_url: "服务器 URL",
|
||||
|
@ -5,7 +5,13 @@ import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import { AppContext } from "./AppContext";
|
||||
|
||||
fetch("config.json")
|
||||
const baseUrl = import.meta.env.BASE_URL;
|
||||
const configJSON = "config.json";
|
||||
// if import.meta.env.BASE_URL have a trailing slash, remove it
|
||||
// load config.json from relative path if import.meta.env.BASE_URL is None or empty
|
||||
const configJSONUrl = baseUrl ? `${baseUrl.replace(/\/$/, "")}/${configJSON}` : configJSON;
|
||||
|
||||
fetch(configJSONUrl)
|
||||
.then(res => res.json())
|
||||
.then(props =>
|
||||
createRoot(document.getElementById("root")).render(
|
||||
|
@ -217,7 +217,6 @@ const LoginPage = () => {
|
||||
disabled={loading || !supportPassAuth}
|
||||
onBlur={handleUsernameChange}
|
||||
resettable
|
||||
fullWidth
|
||||
validate={required()}
|
||||
/>
|
||||
</Box>
|
||||
@ -229,7 +228,6 @@ const LoginPage = () => {
|
||||
autoComplete="current-password"
|
||||
disabled={loading || !supportPassAuth}
|
||||
resettable
|
||||
fullWidth
|
||||
validate={required()}
|
||||
/>
|
||||
</Box>
|
||||
@ -242,7 +240,6 @@ const LoginPage = () => {
|
||||
disabled={loading}
|
||||
readOnly={allowSingleBaseUrl}
|
||||
resettable={allowAnyBaseUrl}
|
||||
fullWidth
|
||||
validate={[required(), validateBaseUrl]}
|
||||
>
|
||||
{allowMultipleBaseUrls &&
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { get } from "lodash";
|
||||
import { MouseEvent } from "react";
|
||||
|
||||
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||
import DestinationsIcon from "@mui/icons-material/CloudQueue";
|
||||
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
||||
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||
import { blue } from "@mui/material/colors";
|
||||
import {
|
||||
Button,
|
||||
Datagrid,
|
||||
@ -27,16 +29,14 @@ import {
|
||||
useNotify,
|
||||
useRefresh,
|
||||
useTranslate,
|
||||
DateFieldProps,
|
||||
} from "react-admin";
|
||||
|
||||
import { DATE_FORMAT } from "../components/date";
|
||||
import { lighten, useTheme } from '@mui/material';
|
||||
|
||||
const DestinationPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||
|
||||
const destinationRowSx = (record: RaRecord) => ({
|
||||
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
|
||||
});
|
||||
|
||||
const destinationFilters = [<SearchInput source="destination" alwaysOn />];
|
||||
|
||||
export const DestinationReconnectButton = () => {
|
||||
@ -92,7 +92,25 @@ const DestinationTitle = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const RetryDateField = (props: DateFieldProps) => {
|
||||
const record = useRecordContext(props);
|
||||
if (props.source && get(record, props.source) === 0) {
|
||||
return <DateField {...props} record={{ ...record, [props.source]: null }} />;
|
||||
}
|
||||
return <DateField {...props} />;
|
||||
};
|
||||
|
||||
export const DestinationList = (props: ListProps) => {
|
||||
const { palette: { error, mode }, } = useTheme();
|
||||
const destinationRowSx = (record: RaRecord) => ({
|
||||
backgroundColor: record.retry_last_ts > 0 ? lighten(error[mode], 0.5) : undefined,
|
||||
"& > td": mode === 'dark' ? {
|
||||
color: record.retry_last_ts > 0 ? "black" : "white",
|
||||
"& > button": {
|
||||
color: blue[700],
|
||||
},
|
||||
} : undefined,
|
||||
});
|
||||
return (
|
||||
<List
|
||||
{...props}
|
||||
@ -103,7 +121,7 @@ export const DestinationList = (props: ListProps) => {
|
||||
<Datagrid rowSx={destinationRowSx} rowClick={id => `${id}/show/rooms`} bulkActionButtons={false}>
|
||||
<TextField source="destination" />
|
||||
<DateField source="failure_ts" showTime options={DATE_FORMAT} />
|
||||
<DateField source="retry_last_ts" showTime options={DATE_FORMAT} />
|
||||
<RetryDateField source="retry_last_ts" showTime options={DATE_FORMAT} />
|
||||
<TextField source="retry_interval" />
|
||||
<TextField source="last_successful_stream_ordering" />
|
||||
<DestinationReconnectButton />
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
useRefresh,
|
||||
useUnselectAll,
|
||||
} from "react-admin";
|
||||
import { useMutation } from "react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import AvatarField from "../components/AvatarField";
|
||||
|
||||
@ -70,13 +70,12 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
|
||||
const refresh = useRefresh();
|
||||
const unselectAllRooms = useUnselectAll("rooms");
|
||||
const dataProvider = useDataProvider();
|
||||
const { mutate, isLoading } = useMutation(
|
||||
() =>
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: () =>
|
||||
dataProvider.createMany("room_directory", {
|
||||
ids: selectedIds,
|
||||
data: {},
|
||||
}),
|
||||
{
|
||||
onSuccess: () => {
|
||||
notify("resources.room_directory.action.send_success");
|
||||
unselectAllRooms();
|
||||
@ -86,11 +85,10 @@ export const RoomDirectoryBulkPublishButton = (props: ButtonProps) => {
|
||||
notify("resources.room_directory.action.send_failure", {
|
||||
type: "error",
|
||||
}),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isLoading}>
|
||||
<Button {...props} label="resources.room_directory.action.create" onClick={mutate} disabled={isPending}>
|
||||
<RoomDirectoryIcon />
|
||||
</Button>
|
||||
);
|
||||
@ -102,6 +100,10 @@ export const RoomDirectoryPublishButton = (props: ButtonProps) => {
|
||||
const refresh = useRefresh();
|
||||
const [create, { isLoading }] = useCreate();
|
||||
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleSend = () => {
|
||||
create(
|
||||
"room_directory",
|
||||
|
@ -47,6 +47,7 @@ import {
|
||||
TopToolbar,
|
||||
NumberField,
|
||||
useListContext,
|
||||
Identifier,
|
||||
} from "react-admin";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
@ -79,17 +80,13 @@ const UserListActions = () => {
|
||||
);
|
||||
};
|
||||
|
||||
UserListActions.defaultProps = {
|
||||
selectedIds: [],
|
||||
onUnselectItems: () => null,
|
||||
};
|
||||
|
||||
const UserPagination = () => <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />;
|
||||
|
||||
const userFilters = [
|
||||
<SearchInput source="name" alwaysOn />,
|
||||
<BooleanInput source="guests" alwaysOn />,
|
||||
<BooleanInput label="resources.users.fields.show_deactivated" source="deactivated" alwaysOn />,
|
||||
<BooleanInput label="resources.users.fields.show_locked" source="locked" alwaysOn />,
|
||||
];
|
||||
|
||||
const UserBulkActionButtons = () => (
|
||||
@ -107,12 +104,15 @@ export const UserList = (props: ListProps) => (
|
||||
<List
|
||||
{...props}
|
||||
filters={userFilters}
|
||||
filterDefaultValues={{ guests: true, deactivated: false }}
|
||||
filterDefaultValues={{ guests: true, deactivated: false, locked: false }}
|
||||
sort={{ field: "name", order: "ASC" }}
|
||||
actions={<UserListActions />}
|
||||
pagination={<UserPagination />}
|
||||
>
|
||||
<Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}>
|
||||
<Datagrid
|
||||
rowClick={(id: Identifier, resource: string) => `/${resource}/${id}`}
|
||||
bulkActionButtons={<UserBulkActionButtons />}
|
||||
>
|
||||
<AvatarField source="avatar_src" sx={{ height: "40px", width: "40px" }} sortBy="avatar_url" />
|
||||
<TextField source="id" sortBy="name" />
|
||||
<TextField source="displayname" />
|
||||
@ -153,7 +153,12 @@ const UserEditActions = () => {
|
||||
};
|
||||
|
||||
export const UserCreate = (props: CreateProps) => (
|
||||
<Create {...props}>
|
||||
<Create
|
||||
{...props}
|
||||
redirect={(resource: string | undefined, id: Identifier | undefined) => {
|
||||
return `${resource}/${id}`;
|
||||
}}
|
||||
>
|
||||
<SimpleForm>
|
||||
<TextInput source="id" autoComplete="off" validate={validateUser} />
|
||||
<TextInput source="displayname" validate={maxLength(256)} />
|
||||
|
@ -30,7 +30,17 @@ describe("authProvider", () => {
|
||||
|
||||
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"}',
|
||||
body: JSON.stringify({
|
||||
device_id: null,
|
||||
initial_device_display_name: "Synapse Admin",
|
||||
type: "m.login.password",
|
||||
user: "@user:example.com",
|
||||
password: "secret",
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: "@user:example.com",
|
||||
}
|
||||
}),
|
||||
headers: new Headers({
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
|
@ -33,6 +33,10 @@ const authProvider: AuthProvider = {
|
||||
type: "m.login.password",
|
||||
user: username,
|
||||
password: password,
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: username,
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { stringify } from "query-string";
|
||||
|
||||
import { DataProvider, DeleteParams, Identifier, Options, RaRecord, fetchUtils } from "react-admin";
|
||||
|
||||
import {
|
||||
DataProvider,
|
||||
DeleteParams,
|
||||
Identifier,
|
||||
Options,
|
||||
PaginationPayload,
|
||||
RaRecord,
|
||||
SortPayload,
|
||||
fetchUtils
|
||||
} from "react-admin";
|
||||
import storage from "../storage";
|
||||
|
||||
// Adds the access token to all requests
|
||||
@ -491,9 +498,9 @@ function getSearchOrder(order: "ASC" | "DESC") {
|
||||
const dataProvider: SynapseDataProvider = {
|
||||
getList: async (resource, params) => {
|
||||
console.log("getList " + resource);
|
||||
const { user_id, name, guests, deactivated, search_term, destination, valid } = params.filter;
|
||||
const { page, perPage } = params.pagination;
|
||||
const { field, order } = params.sort;
|
||||
const { user_id, name, guests, deactivated, locked, search_term, destination, valid } = params.filter;
|
||||
const { page, perPage } = params.pagination as PaginationPayload;
|
||||
const { field, order } = params.sort as SortPayload;
|
||||
const from = (page - 1) * perPage;
|
||||
const query = {
|
||||
from: from,
|
||||
@ -504,6 +511,7 @@ const dataProvider: SynapseDataProvider = {
|
||||
destination: destination,
|
||||
guests: guests,
|
||||
deactivated: deactivated,
|
||||
locked: locked,
|
||||
valid: valid,
|
||||
order_by: field,
|
||||
dir: getSearchOrder(order),
|
||||
|
Loading…
x
Reference in New Issue
Block a user