diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 36dc62d..6837776 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -12,7 +12,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install dependencies
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 1735608..682b201 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -19,11 +19,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -43,7 +43,7 @@ jobs:
esac
echo "::set-output name=tag::$tag"
- name: Build and Push Tag
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v5
with:
context: .
push: true
diff --git a/.github/workflows/edge_ghpage.yml b/.github/workflows/edge_ghpage.yml
index 8d7e992..530ca08 100644
--- a/.github/workflows/edge_ghpage.yml
+++ b/.github/workflows/edge_ghpage.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout 🛎️
uses: actions/checkout@v4
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
node-version: "18"
- name: Install and Build 🔧
diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml
index 03f6f48..930c400 100644
--- a/.github/workflows/github-release.yml
+++ b/.github/workflows/github-release.yml
@@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
node-version: "18"
- run: yarn install
diff --git a/.github/workflows/test-docker-image.yml b/.github/workflows/test-docker-image.yml
index 8ce25eb..778385d 100644
--- a/.github/workflows/test-docker-image.yml
+++ b/.github/workflows/test-docker-image.yml
@@ -19,11 +19,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -43,7 +43,7 @@ jobs:
esac
echo "::set-output name=tag::$tag"
- name: Build and Push Tag
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v5
with:
context: .
push: false
diff --git a/README.md b/README.md
index 4a74a69..95d7d59 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,10 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
### Supported Synapse
-It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.93.0 for all functions to work as expected!
+It needs at least [Synapse](https://github.com/element-hq/synapse) v1.93.0 for all functions to work as expected!
You get your server version with the request `/_synapse/admin/v1/server_version`.
-See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
+See also [Synapse version API](https://element-hq.github.io/synapse/latest/admin_api/version_api.html).
After entering the URL on the login page of synapse-admin the server version appears below the input field.
@@ -27,7 +27,7 @@ You need access to the following endpoints:
- `/_matrix`
- `/_synapse/admin`
-See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints)
+See also [Synapse administration endpoints](https://element-hq.github.io/synapse/latest/reverse_proxy.html#synapse-administration-endpoints)
### Use without install
diff --git a/package.json b/package.json
index 2e019d4..7be3ec3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "synapse-admin",
- "version": "0.8.5",
+ "version": "0.9.0",
"description": "Admin GUI for the Matrix.org server Synapse",
"author": "Awesome Technologies Innovationslabor GmbH",
"license": "Apache-2.0",
@@ -13,27 +13,25 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.4.3",
- "eslint": "^8.48.0",
+ "eslint": "^8.55.0",
"eslint-config-prettier": "^9.0.0",
"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.19.12"
+ "prettier": "^2.2.0"
},
"dependencies": {
- "@emotion/react": "^11.11.1",
- "@emotion/styled": "^11.10.6",
- "@mui/icons-material": "^5.14.8",
+ "@mui/icons-material": "^5.14.19",
"@mui/material": "^5.14.8",
+ "@mui/styles": "5.14.10",
"papaparse": "^5.4.1",
"prop-types": "^15.8.1",
"ra-language-chinese": "^2.0.10",
- "ra-language-french": "^4.13.3",
+ "ra-language-french": "^4.16.2",
"ra-language-german": "^3.13.4",
"ra-language-italian": "^3.13.1",
"react": "^17.0.0",
- "react-admin": "^3.19.12",
+ "react-admin": "^4.16.9",
"react-dom": "^17.0.2",
"react-scripts": "^5.0.1"
},
diff --git a/public/robots.txt b/public/robots.txt
index 01b0f9a..b21f088 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
+Disallow: /
diff --git a/src/App.js b/src/App.js
index 3d6c4b1..1d61d84 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,28 +1,22 @@
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";
-import { UserList, UserCreate, UserEdit } from "./components/users";
-import { RoomList, RoomShow } from "./components/rooms";
-import { ReportList, ReportShow } from "./components/EventReports";
+import users from "./components/users";
+import rooms from "./components/rooms";
+import userMediaStats from "./components/statistics";
+import reports from "./components/EventReports";
+import roomDirectory from "./components/RoomDirectory";
+import destinations from "./components/destinations";
+import registrationToken from "./components/RegistrationTokens";
import LoginPage from "./components/LoginPage";
-import ConfirmationNumberIcon from "@mui/icons-material/ConfirmationNumber";
-import CloudQueueIcon from "@mui/icons-material/CloudQueue";
-import EqualizerIcon from "@mui/icons-material/Equalizer";
-import UserIcon from "@mui/icons-material/Group";
-import { UserMediaStatsList } from "./components/statistics";
-import RoomIcon from "@mui/icons-material/ViewList";
-import ReportIcon from "@mui/icons-material/Warning";
-import FolderSharedIcon from "@mui/icons-material/FolderShared";
-import { DestinationList, DestinationShow } from "./components/destinations";
import { ImportFeature } from "./components/ImportFeature";
-import {
- RegistrationTokenCreate,
- RegistrationTokenEdit,
- RegistrationTokenList,
-} from "./components/RegistrationTokens";
-import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom";
import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en";
@@ -50,47 +44,17 @@ const App = () => (
authProvider={authProvider}
dataProvider={dataProvider}
i18nProvider={i18nProvider}
- customRoutes={[
- ,
- ]}
>
-
-
-
-
-
-
-
+
+ } />
+
+
+
+
+
+
+
+
diff --git a/src/components/AvatarField.js b/src/components/AvatarField.js
new file mode 100644
index 0000000..32dff83
--- /dev/null
+++ b/src/components/AvatarField.js
@@ -0,0 +1,12 @@
+import React from "react";
+import get from "lodash/get";
+import { Avatar } from "@mui/material";
+import { useRecordContext } from "react-admin";
+
+const AvatarField = ({ source, ...rest }) => {
+ const record = useRecordContext(rest);
+ const src = get(record, source)?.toString();
+ return ;
+};
+
+export default AvatarField;
diff --git a/src/components/EventReports.js b/src/components/EventReports.js
index 7e9c2a3..ffece71 100644
--- a/src/components/EventReports.js
+++ b/src/components/EventReports.js
@@ -13,6 +13,7 @@ import {
useTranslate,
} from "react-admin";
import PageviewIcon from "@mui/icons-material/Pageview";
+import ReportIcon from "@mui/icons-material/Warning";
import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = {
@@ -24,8 +25,8 @@ const date_format = {
second: "2-digit",
};
-const ReportPagination = props => (
-
+const ReportPagination = () => (
+
);
export const ReportShow = props => {
@@ -90,7 +91,7 @@ export const ReportShow = props => {
@@ -98,26 +99,32 @@ export const ReportShow = props => {
);
};
-export const ReportList = ({ ...props }) => {
- return (
-
}
- sort={{ field: "received_ts", order: "DESC" }}
- bulkActionButtons={false}
- >
-
-
-
-
-
-
-
-
- );
+export const ReportList = props => (
+
}
+ sort={{ field: "received_ts", order: "DESC" }}
+ >
+
+
+
+
+
+
+
+
+);
+
+const resource = {
+ name: "reports",
+ icon: ReportIcon,
+ list: ReportList,
+ show: ReportShow,
};
+
+export default resource;
diff --git a/src/components/ImportFeature.js b/src/components/ImportFeature.js
index 27bddc1..c0066fe 100644
--- a/src/components/ImportFeature.js
+++ b/src/components/ImportFeature.js
@@ -1,12 +1,6 @@
import React, { useState } from "react";
-import {
- Button as ReactAdminButton,
- useDataProvider,
- useNotify,
- Title,
-} from "react-admin";
+import { useDataProvider, useNotify, Title } from "react-admin";
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
-import GetAppIcon from "@mui/icons-material/GetApp";
import {
Button,
Card,
@@ -23,19 +17,6 @@ import { generateRandomUser } from "./users";
const LOGGING = true;
-export const ImportButton = ({ label, variant = "text" }) => {
- return (
-
-
-
- );
-};
-
const expectedFields = ["id", "displayname"].sort();
const optionalFields = [
"user_type",
@@ -51,7 +32,7 @@ function TranslatableOption({ value, text }) {
return ;
}
-const FilePicker = props => {
+const FilePicker = () => {
const [values, setValues] = useState(null);
const [error, setError] = useState(null);
const [stats, setStats] = useState(null);
@@ -210,7 +191,7 @@ const FilePicker = props => {
return true;
};
- const runImport = async e => {
+ const runImport = async _e => {
if (progress !== null) {
notify("import_users.errors.already_in_progress");
return;
@@ -326,7 +307,7 @@ const FilePicker = props => {
let retries = 0;
const submitRecord = recordData => {
return dataProvider.getOne("users", { id: recordData.id }).then(
- async alreadyExists => {
+ async _alreadyExists => {
if (LOGGING) console.log("already existed");
if (useridMode === "update" || conflictMode === "skip") {
@@ -351,7 +332,7 @@ const FilePicker = props => {
}
}
},
- async okToSubmit => {
+ async _okToSubmit => {
if (LOGGING)
console.log(
"OK to create record " +
diff --git a/src/components/LoginPage.js b/src/components/LoginPage.js
index 573fd4e..8585f69 100644
--- a/src/components/LoginPage.js
+++ b/src/components/LoginPage.js
@@ -1,19 +1,21 @@
import React, { useState, useEffect } from "react";
import {
fetchUtils,
+ Form,
FormDataConsumer,
Notification,
+ required,
useLogin,
useNotify,
- useLocale,
- useSetLocale,
+ useLocaleState,
useTranslate,
PasswordInput,
TextInput,
} from "react-admin";
-import { Form, useForm } from "react-final-form";
+import { useFormContext } from "react-hook-form";
import {
Avatar,
+ Box,
Button,
Card,
CardActions,
@@ -21,66 +23,64 @@ import {
MenuItem,
Select,
TextField,
+ Typography,
} from "@mui/material";
-import { makeStyles } from "@material-ui/core/styles";
+import { styled } from "@mui/material/styles";
import LockIcon from "@mui/icons-material/Lock";
-const useStyles = makeStyles(theme => ({
- 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(Box)(({ 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: {
- backgroundColor: theme.palette.secondary.main,
+ [`& .icon`]: {
+ backgroundColor: theme.palette.grey[500],
},
- hint: {
+ [`& .hint`]: {
marginTop: "1em",
display: "flex",
justifyContent: "center",
- color: theme.palette.grey[500],
+ color: theme.palette.grey[600],
},
- form: {
+ [`& .form`]: {
padding: "0 1em 1em 1em",
},
- input: {
+ [`& .input`]: {
marginTop: "1em",
},
- actions: {
+ [`& .actions`]: {
padding: "0 1em 1em 1em",
},
- serverVersion: {
- color: "#9e9e9e",
+ [`& .serverVersion`]: {
+ color: theme.palette.grey[500],
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
marginBottom: "1em",
marginLeft: "0.5em",
},
}));
-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;
@@ -135,28 +135,16 @@ const LoginPage = ({ theme }) => {
/>
);
- const validate = values => {
- const errors = {};
- if (!values.username) {
- errors.username = translate("ra.validation.required");
- }
- if (!values.password) {
- errors.password = translate("ra.validation.required");
- }
- if (!values.base_url) {
- errors.base_url = translate("ra.validation.required");
+ const validateBaseUrl = value => {
+ if (!value.match(/^(http|https):\/\//)) {
+ return translate("synapseadmin.auth.protocol_error");
+ } else if (
+ !value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)
+ ) {
+ return translate("synapseadmin.auth.url_error");
} else {
- if (!values.base_url.match(/^(http|https):\/\//)) {
- errors.base_url = translate("synapseadmin.auth.protocol_error");
- } else if (
- !values.base_url.match(
- /^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/
- )
- ) {
- errors.base_url = translate("synapseadmin.auth.url_error");
- }
+ return undefined;
}
- return errors;
};
const handleSubmit = auth => {
@@ -191,7 +179,7 @@ const LoginPage = ({ theme }) => {
};
const UserData = ({ formData }) => {
- const form = useForm();
+ const form = useFormContext();
const [serverVersion, setServerVersion] = useState("");
const handleUsernameChange = _ => {
@@ -204,11 +192,11 @@ const LoginPage = ({ theme }) => {
fetchUtils
.fetchJson(wellKnownUrl, { method: "GET" })
.then(({ json }) => {
- form.change("base_url", json["m.homeserver"].base_url);
+ form.setValue("base_url", json["m.homeserver"].base_url);
})
.catch(_ => {
// if there is no .well-known entry, try the home server name
- form.change("base_url", `https://${home_server}`);
+ form.setValue("base_url", `https://${home_server}`);
});
}
};
@@ -265,8 +253,8 @@ const LoginPage = ({ theme }) => {
);
return (
-
-
+ <>
+
{
onBlur={handleUsernameChange}
resettable
fullWidth
+ className="input"
+ validate={required()}
/>
-
-
+
+
{
disabled={loading || !supportPassAuth}
resettable
fullWidth
+ className="input"
+ validate={required()}
/>
-
-
+
+
{
disabled={cfg_base_url || loading}
resettable
fullWidth
+ className="input"
+ validate={[required(), validateBaseUrl]}
/>
-
-
{serverVersion}
-
+
+ {serverVersion}
+ >
);
};
return (
- )}
- />
+ mode="onTouched"
+ >
+
+
+
+ {loading ? (
+
+ ) : (
+
+
+
+ )}
+
+ {translate("synapseadmin.auth.welcome")}
+
+
+
+ {formDataProps => }
+
+
+
+
+
+
+
+
+
+
);
};
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/Menu.js b/src/components/Menu.js
deleted file mode 100644
index 69b0a30..0000000
--- a/src/components/Menu.js
+++ /dev/null
@@ -1,39 +0,0 @@
-// in src/Menu.js
-import * as React from "react";
-import { useSelector } from "react-redux";
-import { useMediaQuery } from "@mui/material";
-import { MenuItemLink, getResources } from "react-admin";
-import DefaultIcon from "@mui/icons-material/ViewList";
-import LabelIcon from "@mui/icons-material/Label";
-
-const Menu = ({ onMenuClick, logout }) => {
- const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs"));
- const open = useSelector(state => state.admin.ui.sidebarOpen);
- const resources = useSelector(getResources);
- return (
-
- {resources.map(resource => (
- : }
- onClick={onMenuClick}
- sidebarIsOpen={open}
- />
- ))}
- }
- onClick={onMenuClick}
- sidebarIsOpen={open}
- />
- {isXSmall && logout}
-
- );
-};
-
-export default Menu;
diff --git a/src/components/RegistrationTokens.js b/src/components/RegistrationTokens.js
index 28213e5..af248ef 100644
--- a/src/components/RegistrationTokens.js
+++ b/src/components/RegistrationTokens.js
@@ -6,18 +6,19 @@ import {
DateField,
DateTimeInput,
Edit,
- Filter,
List,
maxValue,
number,
NumberField,
NumberInput,
regex,
+ SaveButton,
SimpleForm,
TextInput,
TextField,
Toolbar,
} from "react-admin";
+import RegistrationTokenIcon from "@mui/icons-material/ConfirmationNumber";
const date_format = {
year: "numeric",
@@ -53,40 +54,41 @@ const dateFormatter = v => {
return `${year}-${month}-${day}T${hour}:${minute}`;
};
-const RegistrationTokenFilter = props => (
-
-
-
+const registrationTokenFilters = [];
+
+export const RegistrationTokenList = props => (
+
+
+
+
+
+
+
+
+
);
-export const RegistrationTokenList = props => {
- return (
-
}
- filterDefaultValues={{ valid: true }}
- pagination={false}
- perPage={500}
- >
-
-
-
-
-
-
-
-
- );
-};
-
export const RegistrationTokenCreate = props => (
-
- }>
+
+
+ {/* It is possible to create tokens per default without input. */}
+
+
+ }
+ >
(
);
-export const RegistrationTokenEdit = props => {
- return (
-
-
-
-
-
-
-
-
-
- );
+export const RegistrationTokenEdit = props => (
+
+
+
+
+
+
+
+
+
+);
+
+const resource = {
+ name: "users",
+ icon: RegistrationTokenIcon,
+ list: RegistrationTokenList,
+ edit: RegistrationTokenEdit,
+ create: RegistrationTokenCreate,
};
+
+export default resource;
diff --git a/src/components/RoomDirectory.js b/src/components/RoomDirectory.js
index 982e93d..2b338a6 100644
--- a/src/components/RoomDirectory.js
+++ b/src/components/RoomDirectory.js
@@ -1,40 +1,35 @@
-import React, { Fragment } from "react";
-import { Avatar, Chip } from "@mui/material";
-import { connect } from "react-redux";
-import FolderSharedIcon from "@mui/icons-material/FolderShared";
-import { makeStyles } from "@material-ui/core/styles";
+import React from "react";
import {
BooleanField,
BulkDeleteButton,
Button,
- Datagrid,
+ DatagridConfigurable,
+ ExportButton,
DeleteButton,
- Filter,
List,
NumberField,
Pagination,
+ SelectColumnsButton,
TextField,
+ TopToolbar,
useCreate,
- useMutation,
+ useDataProvider,
+ useListContext,
useNotify,
useTranslate,
useRecordContext,
useRefresh,
useUnselectAll,
} from "react-admin";
+import { useMutation } from "react-query";
+import RoomDirectoryIcon from "@mui/icons-material/FolderShared";
+import AvatarField from "./AvatarField";
-const useStyles = makeStyles({
- small: {
- height: "40px",
- width: "40px",
- },
-});
-
-const RoomDirectoryPagination = props => (
-
+const RoomDirectoryPagination = () => (
+
);
-export const RoomDirectoryDeleteButton = props => {
+export const RoomDirectoryUnpublishButton = props => {
const translate = useTranslate();
return (
@@ -50,12 +45,12 @@ export const RoomDirectoryDeleteButton = props => {
smart_count: 1,
})}
resource="room_directory"
- icon={}
+ icon={}
/>
);
};
-export const RoomDirectoryBulkDeleteButton = props => (
+export const RoomDirectoryBulkUnpublishButton = props => (
(
confirmTitle="resources.room_directory.action.title"
confirmContent="resources.room_directory.action.content"
resource="room_directory"
- icon={}
+ icon={}
/>
);
-export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
+export const RoomDirectoryBulkPublishButton = props => {
+ const { selectedIds } = useListContext();
const notify = useNotify();
const refresh = useRefresh();
- const unselectAll = useUnselectAll();
- const [createMany, { loading }] = useMutation();
-
- const handleSend = values => {
- createMany(
- {
- type: "createMany",
- resource: "room_directory",
- payload: { ids: selectedIds, data: {} },
+ const unselectAllRooms = useUnselectAll("rooms");
+ const dataProvider = useDataProvider();
+ const { mutate, isLoading } = useMutation(
+ () =>
+ dataProvider.createMany("room_directory", {
+ ids: selectedIds,
+ data: {},
+ }),
+ {
+ onSuccess: () => {
+ notify("resources.room_directory.action.send_success");
+ unselectAllRooms();
+ refresh();
},
- {
- onSuccess: ({ data }) => {
- notify("resources.room_directory.action.send_success");
- unselectAll("rooms");
- refresh();
- },
- onFailure: error =>
- notify("resources.room_directory.action.send_failure", {
- type: "error",
- }),
- }
- );
- };
+ onError: () =>
+ notify("resources.room_directory.action.send_failure", {
+ type: "error",
+ }),
+ }
+ );
return (
);
};
-export const RoomDirectorySaveButton = props => {
+export const RoomDirectoryPublishButton = props => {
const record = useRecordContext();
const notify = useNotify();
const refresh = useRefresh();
- const [create, { loading }] = useCreate("room_directory");
+ const [create, { isLoading }] = useCreate();
- const handleSend = values => {
+ const handleSend = () => {
create(
+ "room_directory",
+ { data: { id: record.id } },
{
- payload: { data: { id: record.id } },
- },
- {
- onSuccess: ({ data }) => {
+ onSuccess: () => {
notify("resources.room_directory.action.send_success");
refresh();
},
- onFailure: error =>
+ onError: () =>
notify("resources.room_directory.action.send_failure", {
type: "error",
}),
@@ -131,130 +124,83 @@ export const RoomDirectorySaveButton = props => {
return (
);
};
-const RoomDirectoryBulkActionButtons = props => (
-
-
-
+const RoomDirectoryListActions = () => (
+
+
+
+
);
-const AvatarField = ({ source, className, record = {} }) => (
-
-);
-
-const RoomDirectoryFilter = ({ ...props }) => {
- const translate = useTranslate();
- return (
-
-
-
-
-
- );
-};
-
-export const FilterableRoomDirectoryList = ({
- roomDirectoryFilters,
- dispatch,
- ...props
-}) => {
- const classes = useStyles();
- 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 (
-
}
- bulkActionButtons={}
- filters={}
- perPage={100}
+export const RoomDirectoryList = () => (
+
}
+ perPage={100}
+ actions={}
+ >
+ "/rooms/" + id + "/show"}
+ bulkActionButtons={}
+ omit={["room_id", "canonical_alias", "topic"]}
>
- "/rooms/" + id + "/show"}>
-
-
- {roomIdFilter && (
-
- )}
- {canonicalAliasFilter && (
-
- )}
- {topicFilter && (
-
- )}
-
-
-
-
-
- );
+
+
+
+
+
+
+
+
+
+
+);
+
+const resource = {
+ name: "room_directory",
+ icon: RoomDirectoryIcon,
+ list: RoomDirectoryList,
};
-function mapStateToProps(state) {
- return {
- roomDirectoryFilters:
- state.admin.resources.room_directory.list.params.displayedFilters,
- };
-}
-
-export const RoomDirectoryList = connect(mapStateToProps)(
- FilterableRoomDirectoryList
-);
+export default resource;
diff --git a/src/components/ServerNotices.js b/src/components/ServerNotices.js
index 88d67be..ab15825 100644
--- a/src/components/ServerNotices.js
+++ b/src/components/ServerNotices.js
@@ -1,4 +1,4 @@
-import React, { Fragment, useState } from "react";
+import React, { useState } from "react";
import {
Button,
SaveButton,
@@ -7,12 +7,14 @@ import {
Toolbar,
required,
useCreate,
- useMutation,
+ useDataProvider,
+ 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 {
@@ -22,7 +24,7 @@ import {
DialogTitle,
} from "@mui/material";
-const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
+const ServerNoticeDialog = ({ open, loading, onClose, onSubmit }) => {
const translate = useTranslate();
const ServerNoticeToolbar = props => (
@@ -46,12 +48,7 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
{translate("resources.servernotices.helper.send")}
- }
- submitOnEnter={false}
- redirect={false}
- save={onSend}
- >
+ } onSubmit={onSubmit}>
{
);
};
-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();
const handleDialogOpen = () => setOpen(true);
const handleDialogClose = () => setOpen(false);
const handleSend = values => {
create(
- { payload: { data: { id: record.id, ...values } } },
+ "servernotices",
+ { data: { id: record.id, ...values } },
{
onSuccess: () => {
notify("resources.servernotices.action.send_success");
handleDialogClose();
},
- onFailure: () =>
+ onError: () =>
notify("resources.servernotices.action.send_failure", {
type: "error",
}),
@@ -93,67 +91,65 @@ export const ServerNoticeButton = props => {
};
return (
-
+ <>
-
+ >
);
};
-export const ServerNoticeBulkButton = ({ selectedIds }) => {
+export const ServerNoticeBulkButton = () => {
+ const { selectedIds } = useListContext();
const [open, setOpen] = useState(false);
+ const openDialog = () => setOpen(true);
+ const closeDialog = () => setOpen(false);
const notify = useNotify();
- const unselectAll = useUnselectAll();
- const [createMany, { loading }] = useMutation();
+ const unselectAllUsers = useUnselectAll("users");
+ const dataProvider = useDataProvider();
- const handleDialogOpen = () => setOpen(true);
- const handleDialogClose = () => setOpen(false);
-
- const handleSend = values => {
- createMany(
- {
- type: "createMany",
- resource: "servernotices",
- payload: { ids: selectedIds, data: values },
+ const { mutate: sendNotices, isLoading } = useMutation(
+ data =>
+ dataProvider.createMany("servernotices", {
+ ids: selectedIds,
+ data: data,
+ }),
+ {
+ onSuccess: () => {
+ notify("resources.servernotices.action.send_success");
+ unselectAllUsers();
+ closeDialog();
},
- {
- onSuccess: ({ data }) => {
- notify("resources.servernotices.action.send_success");
- unselectAll("users");
- handleDialogClose();
- },
- onFailure: error =>
- notify("resources.servernotices.action.send_failure", {
- type: "error",
- }),
- }
- );
- };
+ onError: () =>
+ notify("resources.servernotices.action.send_failure", {
+ type: "error",
+ }),
+ }
+ );
return (
-
+ <>
-
+ >
);
};
diff --git a/src/components/destinations.js b/src/components/destinations.js
index c8c9f8d..3fc7f78 100644
--- a/src/components/destinations.js
+++ b/src/components/destinations.js
@@ -3,7 +3,6 @@ import {
Button,
Datagrid,
DateField,
- Filter,
List,
Pagination,
ReferenceField,
@@ -21,11 +20,12 @@ import {
useTranslate,
} from "react-admin";
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";
-const DestinationPagination = props => (
-
+const DestinationPagination = () => (
+
);
const date_format = {
@@ -37,23 +37,17 @@ const date_format = {
second: "2-digit",
};
-const destinationRowStyle = (record, index) => ({
+const destinationRowSx = (record, _index) => ({
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
});
-const DestinationFilter = ({ ...props }) => {
- return (
-
-
-
- );
-};
+const destinationFilters = [];
-export const DestinationReconnectButton = props => {
+export const DestinationReconnectButton = () => {
const record = useRecordContext();
const refresh = useRefresh();
const notify = useNotify();
- const [handleReconnect, { isLoading }] = useDelete("destinations");
+ const [handleReconnect, { isLoading }] = useDelete();
// Reconnect is not required if no error has occurred. (`failure_ts`)
if (!record || !record.failure_ts) return null;
@@ -63,7 +57,8 @@ export const DestinationReconnectButton = props => {
e.stopPropagation();
handleReconnect(
- { payload: { id: record.id } },
+ "destinations",
+ { id: record.id },
{
onSuccess: () => {
notify("ra.notification.updated", {
@@ -71,7 +66,7 @@ export const DestinationReconnectButton = props => {
});
refresh();
},
- onFailure: () => {
+ onError: () => {
notify("ra.message.error", { type: "error" });
},
}
@@ -89,13 +84,13 @@ export const DestinationReconnectButton = props => {
);
};
-const DestinationShowActions = props => (
+const DestinationShowActions = () => (
);
-const DestinationTitle = props => {
+const DestinationTitle = () => {
const record = useRecordContext();
const translate = useTranslate();
return (
@@ -109,14 +104,14 @@ export const DestinationList = props => {
return (
}
+ filters={destinationFilters}
pagination={}
sort={{ field: "destination", order: "ASC" }}
- bulkActionButtons={false}
>
`${basePath}/${id}/show/rooms`}
+ rowSx={destinationRowSx}
+ rowClick={(id, _resource, _record) => `${id}/show/rooms`}
+ bulkActionButtons={false}
>
@@ -160,7 +155,7 @@ export const DestinationShow = props => {
>
`/rooms/${id}/show`}
+ rowClick={(id, resource, record) => `/rooms/${id}/show`}
>
{
);
};
+
+const resource = {
+ name: "destinations",
+ icon: DestinationsIcon,
+ list: DestinationList,
+ show: DestinationShow,
+};
+
+export default resource;
diff --git a/src/components/devices.js b/src/components/devices.js
index b3473d3..7a5069a 100644
--- a/src/components/devices.js
+++ b/src/components/devices.js
@@ -1,4 +1,4 @@
-import React, { Fragment, useState } from "react";
+import React, { useState } from "react";
import {
Button,
useDelete,
@@ -8,34 +8,16 @@ import {
useRefresh,
} from "react-admin";
import ActionDelete from "@mui/icons-material/Delete";
-import { makeStyles } from "@material-ui/core/styles";
-import { alpha } from "@mui/material/styles";
-import classnames from "classnames";
-
-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" }
-);
+import { alpha, useTheme } from "@mui/material/styles";
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();
- const [removeDevice, { isLoading }] = useDelete("devices");
+ const [removeDevice, { isLoading }] = useDelete();
if (!record) return null;
@@ -44,13 +26,15 @@ export const DeviceRemoveButton = props => {
const handleConfirm = () => {
removeDevice(
- { payload: { id: record.id, user_id: record.user_id } },
+ "devices",
+ // needs previousData for user_id
+ { id: record.id, previousData: record },
{
onSuccess: () => {
notify("resources.devices.action.erase.success");
refresh();
},
- onFailure: () => {
+ onError: () => {
notify("resources.devices.action.erase.failure", { type: "error" });
},
}
@@ -59,11 +43,21 @@ export const DeviceRemoveButton = props => {
};
return (
-
+ <>
@@ -79,6 +73,6 @@ export const DeviceRemoveButton = props => {
name: record.display_name ? record.display_name : record.id,
}}
/>
-
+ >
);
};
diff --git a/src/components/media.js b/src/components/media.js
index 09a1377..cc06a0b 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 React, { useState } from "react";
import {
BooleanInput,
Button,
@@ -30,24 +27,9 @@ import {
import IconCancel from "@mui/icons-material/Cancel";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
+import { alpha, useTheme } from "@mui/material/styles";
-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 DeleteMediaDialog = ({ open, loading, onClose, onSubmit }) => {
const translate = useTranslate();
const dateParser = v => {
@@ -56,19 +38,17 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
return d.getTime();
};
- const DeleteMediaToolbar = props => {
- return (
-
- }
- />
-
-
- );
- };
+ const DeleteMediaToolbar = props => (
+
+ }
+ />
+
+
+ );
return (