diff --git a/package.json b/package.json index 610cf2c..8dacfd3 100644 --- a/package.json +++ b/package.json @@ -18,22 +18,20 @@ "eslint-config-react-app": "^7.0.1", "eslint-plugin-prettier": "^4.2.1", "jest-fetch-mock": "^3.0.3", - "prettier": "^2.2.0", - "ra-test": "^3.15.0" + "prettier": "^2.2.0" }, "dependencies": { "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", "@mui/icons-material": "^5.3.1", "@mui/material": "^5.11.7", - "@mui/styles": "^5.11.7", "papaparse": "^5.2.0", "prop-types": "^15.7.2", "ra-language-chinese": "^2.0.10", "ra-language-french": "^4.2.0", "ra-language-german": "^3.13.4", "react": "^17.0.0", - "react-admin": "^3.19.7", + "react-admin": "^4.7.3", "react-dom": "^17.0.2", "react-scripts": "^5.0.1" }, diff --git a/src/App.js b/src/App.js index 0aca651..40e1ce3 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ import React from "react"; -import { Admin, Resource, resolveBrowserLocale } from "react-admin"; +import { + Admin, + CustomRoutes, + Resource, + resolveBrowserLocale, +} from "react-admin"; import polyglotI18nProvider from "ra-i18n-polyglot"; import authProvider from "./synapse/authProvider"; import dataProvider from "./synapse/dataProvider"; @@ -48,10 +53,10 @@ const App = () => ( authProvider={authProvider} dataProvider={dataProvider} i18nProvider={i18nProvider} - customRoutes={[ - , - ]} > + + } /> + ({ - main: { - display: "flex", - flexDirection: "column", - minHeight: "calc(100vh - 1em)", - alignItems: "center", - justifyContent: "flex-start", - background: "url(./images/floating-cogs.svg)", - backgroundColor: "#f9f9f9", - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - }, - card: { +const FormBox = styled("div")(({ theme }) => ({ + display: "flex", + flexDirection: "column", + minHeight: "calc(100vh - 1em)", + alignItems: "center", + justifyContent: "flex-start", + background: "url(./images/floating-cogs.svg)", + backgroundColor: "#f9f9f9", + backgroundRepeat: "no-repeat", + backgroundSize: "cover", + + [`& .card`]: { minWidth: "30em", marginTop: "6em", marginBottom: "6em", }, - avatar: { + [`& .avatar`]: { margin: "1em", display: "flex", justifyContent: "center", }, - icon: { + [`& .icon`]: { backgroundColor: theme.palette.secondary.main, }, - hint: { + [`& .hint`]: { marginTop: "1em", display: "flex", justifyContent: "center", color: theme.palette.grey[500], }, - form: { + [`& .form`]: { padding: "0 1em 1em 1em", }, - input: { + [`& .input`]: { marginTop: "1em", }, - actions: { + [`& .actions`]: { padding: "0 1em 1em 1em", }, - serverVersion: { + [`& .serverVersion`]: { color: "#9e9e9e", fontFamily: "Roboto, Helvetica, Arial, sans-serif", marginBottom: "1em", @@ -73,14 +73,12 @@ const useStyles = makeStyles(theme => ({ }, })); -const LoginPage = ({ theme }) => { - const classes = useStyles({ theme }); +const LoginPage = () => { const login = useLogin(); const notify = useNotify(); const [loading, setLoading] = useState(false); const [supportPassAuth, setSupportPassAuth] = useState(true); - var locale = useLocale(); - const setLocale = useSetLocale(); + const [locale, setLocale] = useLocaleState(); const translate = useTranslate(); const base_url = localStorage.getItem("base_url"); const cfg_base_url = process.env.REACT_APP_SERVER; @@ -263,42 +261,39 @@ const LoginPage = ({ theme }) => { ); return ( - - - - - - - - - - - {serverVersion} - + + + + + {serverVersion} + ); }; @@ -307,66 +302,60 @@ const LoginPage = ({ theme }) => { initialValues={{ base_url: cfg_base_url || base_url }} onSubmit={handleSubmit} validate={validate} - render={({ handleSubmit }) => ( - - - - - - - - - - {translate("synapseadmin.auth.welcome")} - - - - { - setLocale(e.target.value); - }} - fullWidth - disabled={loading} - > - Deutsch - English - Français - 简体中文 - - - - {formDataProps => } - - - - - {loading && } - {translate("ra.auth.sign_in")} - - - {loading && } - {translate("synapseadmin.auth.sso_sign_in")} - - - - - - - )} - /> + > + + + + + + + + {translate("synapseadmin.auth.welcome")} + + { + setLocale(e.target.value); + }} + fullWidth + disabled={loading} + className="input" + > + Deutsch + English + Français + 简体中文 + + + {formDataProps => } + + + + {loading && } + {translate("ra.auth.sign_in")} + + + {loading && } + {translate("synapseadmin.auth.sso_sign_in")} + + + + + + + ); }; diff --git a/src/components/LoginPage.test.js b/src/components/LoginPage.test.js index 00c1267..a336e2d 100644 --- a/src/components/LoginPage.test.js +++ b/src/components/LoginPage.test.js @@ -1,14 +1,14 @@ import React from "react"; import { render } from "@testing-library/react"; -import { TestContext } from "ra-test"; +import { AdminContext } from "react-admin"; import LoginPage from "./LoginPage"; describe("LoginForm", () => { it("renders", () => { render( - + - + ); }); }); diff --git a/src/components/RoomDirectory.js b/src/components/RoomDirectory.js index d381d8f..b0df8ef 100644 --- a/src/components/RoomDirectory.js +++ b/src/components/RoomDirectory.js @@ -1,26 +1,28 @@ import React, { Fragment } from "react"; -import { Avatar, Chip } from "@mui/material"; -import { connect } from "react-redux"; +import { Avatar } from "@mui/material"; import FolderSharedIcon from "@mui/icons-material/FolderShared"; import { BooleanField, BulkDeleteButton, Button, - Datagrid, + DatagridConfigurable, + ExportButton, DeleteButton, - Filter, List, NumberField, Pagination, + SelectColumnsButton, TextField, + TopToolbar, useCreate, - useMutation, + useListContext, useNotify, useTranslate, useRecordContext, useRefresh, useUnselectAll, } from "react-admin"; +import { useMutation } from "react-query"; const RoomDirectoryPagination = props => ( @@ -59,26 +61,23 @@ export const RoomDirectoryBulkDeleteButton = props => ( /> ); -export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => { +export const RoomDirectoryBulkSaveButton = () => { + const { selectedIds } = useListContext(); const notify = useNotify(); const refresh = useRefresh(); const unselectAll = useUnselectAll(); - const [createMany, { loading }] = useMutation(); + const { createMany, isloading } = useMutation(); const handleSend = values => { createMany( + ["room_directory", "createMany", { ids: selectedIds, data: {} }], { - type: "createMany", - resource: "room_directory", - payload: { ids: selectedIds, data: {} }, - }, - { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.room_directory.action.send_success"); unselectAll("rooms"); refresh(); }, - onFailure: error => + onError: error => notify("resources.room_directory.action.send_failure", { type: "error", }), @@ -90,18 +89,18 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => { ); }; -export const RoomDirectorySaveButton = props => { +export const RoomDirectorySaveButton = () => { const record = useRecordContext(); const notify = useNotify(); const refresh = useRefresh(); - const [create, { loading }] = useCreate("room_directory"); + const [create, { isloading }] = useCreate("room_directory"); const handleSend = values => { create( @@ -109,11 +108,11 @@ export const RoomDirectorySaveButton = props => { payload: { data: { id: record.id } }, }, { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.room_directory.action.send_success"); refresh(); }, - onFailure: error => + onError: error => notify("resources.room_directory.action.send_failure", { type: "error", }), @@ -125,16 +124,16 @@ export const RoomDirectorySaveButton = props => { ); }; -const RoomDirectoryBulkActionButtons = props => ( +const RoomDirectoryBulkActionButtons = () => ( - + ); @@ -142,110 +141,76 @@ const AvatarField = ({ source, className, record = {} }) => ( ); -const RoomDirectoryFilter = ({ ...props }) => { - const translate = useTranslate(); - return ( - - - - - - ); -}; +const RoomDirectoryListActions = () => ( + + + + +); -export const FilterableRoomDirectoryList = ({ - roomDirectoryFilters, - dispatch, - ...props -}) => { +export const RoomDirectoryList = () => ( + /* const filter = roomDirectoryFilters; const roomIdFilter = filter && filter.room_id ? true : false; const topicFilter = filter && filter.topic ? true : false; const canonicalAliasFilter = filter && filter.canonical_alias ? true : false; + */ - return ( - } + } + perPage={100} + actions={} + > + "/rooms/" + id + "/show"} bulkActionButtons={} - filters={} - perPage={100} + omit={["room_id", "canonical_alias", "topic"]} > - "/rooms/" + id + "/show"}> - - - {roomIdFilter && ( - - )} - {canonicalAliasFilter && ( - - )} - {topicFilter && ( - - )} - - - - - - ); -}; + + -function mapStateToProps(state) { - return { - roomDirectoryFilters: - state.admin.resources.room_directory.list.params.displayedFilters, - }; -} + -export const RoomDirectoryList = connect(mapStateToProps)( - FilterableRoomDirectoryList + + + + + + + + + ); diff --git a/src/components/ServerNotices.js b/src/components/ServerNotices.js index 88d67be..9ce675e 100644 --- a/src/components/ServerNotices.js +++ b/src/components/ServerNotices.js @@ -7,12 +7,13 @@ import { Toolbar, required, useCreate, - useMutation, + useListContext, useNotify, useRecordContext, useTranslate, useUnselectAll, } from "react-admin"; +import { useMutation } from "react-query"; import MessageIcon from "@mui/icons-material/Message"; import IconCancel from "@mui/icons-material/Cancel"; import { @@ -48,7 +49,6 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => { } - submitOnEnter={false} redirect={false} save={onSend} > @@ -67,11 +67,11 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => { ); }; -export const ServerNoticeButton = props => { +export const ServerNoticeButton = () => { const record = useRecordContext(); const [open, setOpen] = useState(false); const notify = useNotify(); - const [create, { loading }] = useCreate("servernotices"); + const [create, { isloading }] = useCreate("servernotices"); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); @@ -84,7 +84,7 @@ export const ServerNoticeButton = props => { notify("resources.servernotices.action.send_success"); handleDialogClose(); }, - onFailure: () => + onError: () => notify("resources.servernotices.action.send_failure", { type: "error", }), @@ -97,7 +97,7 @@ export const ServerNoticeButton = props => { @@ -110,29 +110,26 @@ export const ServerNoticeButton = props => { ); }; -export const ServerNoticeBulkButton = ({ selectedIds }) => { +export const ServerNoticeBulkButton = () => { + const { selectedIds } = useListContext(); const [open, setOpen] = useState(false); const notify = useNotify(); const unselectAll = useUnselectAll(); - const [createMany, { loading }] = useMutation(); + const { createMany, isloading } = useMutation(); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); const handleSend = values => { createMany( + ["servernotices", "createMany", { ids: selectedIds, data: values }], { - type: "createMany", - resource: "servernotices", - payload: { ids: selectedIds, data: values }, - }, - { - onSuccess: ({ data }) => { + onSuccess: data => { notify("resources.servernotices.action.send_success"); unselectAll("users"); handleDialogClose(); }, - onFailure: error => + onError: error => notify("resources.servernotices.action.send_failure", { type: "error", }), @@ -141,11 +138,11 @@ export const ServerNoticeBulkButton = ({ selectedIds }) => { }; return ( - + <> @@ -154,6 +151,6 @@ export const ServerNoticeBulkButton = ({ selectedIds }) => { onClose={handleDialogClose} onSend={handleSend} /> - + > ); }; diff --git a/src/components/destinations.js b/src/components/destinations.js index c8c9f8d..4ebe3f9 100644 --- a/src/components/destinations.js +++ b/src/components/destinations.js @@ -71,7 +71,7 @@ export const DestinationReconnectButton = props => { }); refresh(); }, - onFailure: () => { + onError: () => { notify("ra.message.error", { type: "error" }); }, } @@ -112,11 +112,11 @@ export const DestinationList = props => { filters={} pagination={} sort={{ field: "destination", order: "ASC" }} - bulkActionButtons={false} > `${basePath}/${id}/show/rooms`} + rowClick={(id, resource, record) => `${id}/show/rooms`} + bulkActionButtons={false} > @@ -160,7 +160,7 @@ export const DestinationShow = props => { > `/rooms/${id}/show`} + rowClick={(id, resource, record) => `/rooms/${id}/show`} > ({ - deleteButton: { - color: theme.palette.error.main, - "&:hover": { - backgroundColor: alpha(theme.palette.error.main, 0.12), - // Reset on mouse devices - "@media (hover: none)": { - backgroundColor: "transparent", - }, - }, - }, - }), - { name: "RaDeleteDeviceButton" } -); export const DeviceRemoveButton = props => { + const theme = useTheme(); const record = useRecordContext(); - const classes = useStyles(props); const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); @@ -63,7 +45,16 @@ export const DeviceRemoveButton = props => { diff --git a/src/components/media.js b/src/components/media.js index 09a1377..cee1958 100644 --- a/src/components/media.js +++ b/src/components/media.js @@ -1,7 +1,4 @@ import React, { Fragment, useState } from "react"; -import classnames from "classnames"; -import { alpha } from "@mui/material/styles"; -import { makeStyles } from "@material-ui/core/styles"; import { BooleanInput, Button, @@ -17,6 +14,7 @@ import { useRefresh, useTranslate, } from "react-admin"; +import { alpha, useTheme } from "@mui/material/styles"; import BlockIcon from "@mui/icons-material/Block"; import ClearIcon from "@mui/icons-material/Clear"; import DeleteSweepIcon from "@mui/icons-material/DeleteSweep"; @@ -31,22 +29,6 @@ import IconCancel from "@mui/icons-material/Cancel"; import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; -const useStyles = makeStyles( - theme => ({ - deleteButton: { - color: theme.palette.error.main, - "&:hover": { - backgroundColor: alpha(theme.palette.error.main, 0.12), - // Reset on mouse devices - "@media (hover: none)": { - backgroundColor: "transparent", - }, - }, - }, - }), - { name: "RaDeleteDeviceButton" } -); - const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { const translate = useTranslate(); @@ -81,7 +63,6 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { } - submitOnEnter={false} redirect={false} save={onSend} > @@ -113,10 +94,10 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => { }; export const DeleteMediaButton = props => { - const classes = useStyles(props); + const theme = useTheme(); const [open, setOpen] = useState(false); const notify = useNotify(); - const [deleteOne, { loading }] = useDelete("delete_media"); + const [deleteOne, { isLoading }] = useDelete("delete_media"); const handleDialogOpen = () => setOpen(true); const handleDialogClose = () => setOpen(false); @@ -129,7 +110,7 @@ export const DeleteMediaButton = props => { notify("resources.delete_media.action.send_success"); handleDialogClose(); }, - onFailure: () => + onError: () => notify("resources.delete_media.action.send_failure", { type: "error", }), @@ -142,8 +123,17 @@ export const DeleteMediaButton = props => { @@ -174,7 +164,7 @@ export const ProtectMediaButton = props => { notify("resources.protect_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.protect_media.action.send_failure", { type: "error", }), @@ -190,7 +180,7 @@ export const ProtectMediaButton = props => { notify("resources.protect_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.protect_media.action.send_failure", { type: "error", }), @@ -270,7 +260,7 @@ export const QuarantineMediaButton = props => { notify("resources.quarantine_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.quarantine_media.action.send_failure", { type: "error", }), @@ -286,7 +276,7 @@ export const QuarantineMediaButton = props => { notify("resources.quarantine_media.action.send_success"); refresh(); }, - onFailure: () => + onError: () => notify("resources.quarantine_media.action.send_failure", { type: "error", }), diff --git a/src/components/rooms.js b/src/components/rooms.js index ecdbe74..f09a887 100644 --- a/src/components/rooms.js +++ b/src/components/rooms.js @@ -1,11 +1,12 @@ import React, { Fragment } from "react"; -import { connect } from "react-redux"; import { BooleanField, BulkDeleteButton, DateField, Datagrid, + DatagridConfigurable, DeleteButton, + ExportButton, Filter, List, NumberField, @@ -13,6 +14,7 @@ import { ReferenceField, ReferenceManyField, SearchInput, + SelectColumnsButton, SelectField, Show, Tab, @@ -54,8 +56,9 @@ const RoomPagination = props => ( ); -const EncryptionField = ({ source, record = {}, emptyText }) => { +const EncryptionField = ({ source, emptyText }) => { const translate = useTranslate(); + const record = useRecordContext(); const value = get(record, source); let ariaLabel = value === false ? "ra.boolean.false" : "ra.boolean.true"; @@ -95,7 +98,7 @@ const RoomTitle = props => { ); }; -const RoomShowActions = ({ basePath, data, resource }) => { +const RoomShowActions = ({ data, resource }) => { var roomDirectoryStatus = ""; if (data) { roomDirectoryStatus = data.public; @@ -110,7 +113,6 @@ const RoomShowActions = ({ basePath, data, resource }) => { )} { > "/users/" + id} + rowClick={(id, resource, record) => "/users/" + id} > { ); }; -const RoomBulkActionButtons = props => ( +const RoomBulkActionButtons = () => ( - - + + { - const filter = roomFilters; - const localMembersFilter = - filter && filter.joined_local_members ? true : false; - const stateEventsFilter = filter && filter.state_events ? true : false; - const versionFilter = filter && filter.version ? true : false; - const federateableFilter = filter && filter.federatable ? true : false; +const RoomListActions = () => ( + + + + +); - return ( - } - sort={{ field: "name", order: "ASC" }} - filters={} +export const RoomList = () => ( + } + sort={{ field: "name", order: "ASC" }} + filters={} + actions={} + > + } + omit={["joined_local_members", "state_events", "version", "federatable"]} > - - } - /> - - - {localMembersFilter && } - {stateEventsFilter && } - {versionFilter && } - {federateableFilter && } - - - - ); -}; + } + /> -function mapStateToProps(state) { - return { - roomFilters: state.admin.resources.rooms.list.params.displayedFilters, - }; -} - -export const RoomList = connect(mapStateToProps)(FilterableRoomList); + + + + + + + + + +); diff --git a/src/components/statistics.js b/src/components/statistics.js index 74e6b70..603cba1 100644 --- a/src/components/statistics.js +++ b/src/components/statistics.js @@ -65,9 +65,11 @@ export const UserMediaStatsList = props => { filters={} pagination={} sort={{ field: "media_length", order: "DESC" }} - bulkActionButtons={false} > - "/users/" + id + "/media"}> + "/users/" + id + "/media"} + bulkActionButtons={false} + > { - return { - pathname: "/import_users", - }; -}; - const choices_medium = [ { id: "email", name: "resources.users.email" }, { id: "msisdn", name: "resources.users.msisdn" }, @@ -88,7 +82,6 @@ const UserListActions = ({ filterValues, permanentFilter, hasCreate, // you can hide CreateButton if hasCreate = false - basePath, selectedIds, onUnselectItems, showFilter, @@ -106,7 +99,7 @@ const UserListActions = ({ filterValues, context: "button", })} - + {/* Add your custom actions */} - + @@ -144,16 +137,15 @@ const UserFilter = props => ( ); -const UserBulkActionButtons = props => ( - - +const UserBulkActionButtons = () => ( + <> + - + > ); const AvatarField = ({ source, record = {}, sx }) => ( @@ -168,10 +160,9 @@ export const UserList = props => { filterDefaultValues={{ guests: true, deactivated: false }} sort={{ field: "name", order: "ASC" }} actions={} - bulkActionButtons={} pagination={} > - + }> ( - + ); @@ -288,7 +279,6 @@ export const UserCreate = props => ( source="user_type" choices={choices_type} translateChoice={false} - allowEmpty={true} resettable /> @@ -354,7 +344,6 @@ export const UserEdit = props => { source="user_type" choices={choices_type} translateChoice={false} - allowEmpty={true} resettable /> @@ -498,7 +487,7 @@ export const UserEdit = props => { > "/rooms/" + id + "/show"} + rowClick={(id, resource, record) => "/rooms/" + id + "/show"} >