diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index dd0ecc5..6837776 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup node
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: 16
+ node-version: "18"
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Run tests
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 87a9ffb..682b201 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -17,13 +17,13 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ 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@v3
+ 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 ee7e8f4..530ca08 100644
--- a/.github/workflows/edge_ghpage.yml
+++ b/.github/workflows/edge_ghpage.yml
@@ -10,17 +10,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
- uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
- node-version: "16"
+ node-version: "18"
- name: Install and Build 🔧
run: |
yarn install
yarn build
- name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.4.1
+ uses: JamesIves/github-pages-deploy-action@v4.4.3
with:
branch: gh-pages
folder: build
diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml
index b3dd7eb..930c400 100644
--- a/.github/workflows/github-release.yml
+++ b/.github/workflows/github-release.yml
@@ -13,10 +13,10 @@ jobs:
packages: write
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-node@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
- node-version: "16"
+ node-version: "18"
- run: yarn install
- run: yarn build
- run: |
diff --git a/.github/workflows/test-docker-image.yml b/.github/workflows/test-docker-image.yml
new file mode 100644
index 0000000..778385d
--- /dev/null
+++ b/.github/workflows/test-docker-image.yml
@@ -0,0 +1,51 @@
+name: Test docker image creation
+
+on:
+ push:
+ # Sequence of patterns matched against refs/heads
+ # prettier-ignore
+ branches:
+ # Push events on branch fix_docker_cd
+ - fix_docker_cd
+ # Sequence of patterns matched against refs/tags
+ tags:
+ - '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Calculate docker image tag
+ id: set-tag
+ run: |
+ case "${GITHUB_REF}" in
+ refs/heads/master|refs/heads/main)
+ tag=latest
+ ;;
+ refs/tags/*)
+ tag=${GITHUB_REF#refs/tags/}
+ ;;
+ *)
+ tag=${GITHUB_SHA}
+ ;;
+ esac
+ echo "::set-output name=tag::$tag"
+ - name: Build and Push Tag
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ push: false
+ tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
+ platforms: linux/amd64,linux/arm64
diff --git a/.travis.yml b/.travis.yml
index 0ca87f9..6a52ea4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
dist: focal
language: node_js
node_js:
- - 17
+ - 18
cache: yarn
diff --git a/Dockerfile b/Dockerfile
index a5d5171..93de6c7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,13 @@
# Builder
FROM node:lts as builder
-ARG PUBLIC_URL=/
ARG REACT_APP_SERVER
WORKDIR /src
COPY . /src
-RUN yarn --network-timeout=100000 install
-RUN PUBLIC_URL=$PUBLIC_URL REACT_APP_SERVER=$REACT_APP_SERVER yarn build
+RUN yarn --network-timeout=300000 install
+RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
# App
diff --git a/README.md b/README.md
index 50486cc..9f6da7e 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.71.0 for all functions to work as expected!
+It needs at least [Synapse](https://github.com/element-hq/synapse) v1.71.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/docker-compose.yml b/docker-compose.yml
index df52d3e..6e07aa2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,7 +16,7 @@ services:
# if you're building on an architecture other than amd64, make sure
# to define a maximum ram for node. otherwise the build will fail.
# - NODE_OPTIONS="--max_old_space_size=1024"
- # default is /
+ # default is .
# - PUBLIC_URL=/synapse-admin
# You can use a fixed homeserver, so that the user can no longer
# define it himself
diff --git a/package.json b/package.json
index 87c157e..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",
@@ -11,23 +11,27 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
- "@testing-library/react": "^11.2.6",
+ "@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.4.3",
- "eslint": "^8.32.0",
- "eslint-config-prettier": "^8.3.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.15.0"
+ "prettier": "^2.2.0"
},
"dependencies": {
- "papaparse": "^5.2.0",
- "prop-types": "^15.7.2",
+ "@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.16.2",
"ra-language-german": "^3.13.4",
+ "ra-language-italian": "^3.13.1",
"react": "^17.0.0",
- "react-admin": "^3.19.7",
+ "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 7a27d0c..1d61d84 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,37 +1,35 @@
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 "@material-ui/icons/ConfirmationNumber";
-import CloudQueueIcon from "@material-ui/icons/CloudQueue";
-import EqualizerIcon from "@material-ui/icons/Equalizer";
-import UserIcon from "@material-ui/icons/Group";
-import { UserMediaStatsList } from "./components/statistics";
-import RoomIcon from "@material-ui/icons/ViewList";
-import ReportIcon from "@material-ui/icons/Warning";
-import FolderSharedIcon from "@material-ui/icons/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";
+import frenchMessages from "./i18n/fr";
import chineseMessages from "./i18n/zh";
+import italianMessages from "./i18n/it";
// TODO: Can we use lazy loading together with browser locale?
const messages = {
de: germanMessages,
en: englishMessages,
+ fr: frenchMessages,
+ it: italianMessages,
zh: chineseMessages,
};
const i18nProvider = polyglotI18nProvider(
@@ -46,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 87cebd7..ffece71 100644
--- a/src/components/EventReports.js
+++ b/src/components/EventReports.js
@@ -12,8 +12,9 @@ import {
TextField,
useTranslate,
} from "react-admin";
-import PageviewIcon from "@material-ui/icons/Pageview";
-import ViewListIcon from "@material-ui/icons/ViewList";
+import PageviewIcon from "@mui/icons-material/Pageview";
+import ReportIcon from "@mui/icons-material/Warning";
+import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = {
year: "numeric",
@@ -24,8 +25,8 @@ const date_format = {
second: "2-digit",
};
-const ReportPagination = props => (
-
+const ReportPagination = () => (
+
);
export const ReportShow = props => {
@@ -79,6 +80,7 @@ export const ReportShow = props => {
+
@@ -89,7 +91,7 @@ export const ReportShow = props => {
@@ -97,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 565855d..c0066fe 100644
--- a/src/components/ImportFeature.js
+++ b/src/components/ImportFeature.js
@@ -1,41 +1,22 @@
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 "@material-ui/icons/GetApp";
import {
Button,
Card,
CardActions,
CardContent,
CardHeader,
- FormControlLabel,
Checkbox,
+ Container,
+ FormControlLabel,
NativeSelect,
-} from "@material-ui/core";
+} from "@mui/material";
import { useTranslate } from "ra-core";
-import Container from "@material-ui/core/Container/Container";
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 b31cae3..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,
-} from "@material-ui/core";
-import { makeStyles } from "@material-ui/core/styles";
-import LockIcon from "@material-ui/icons/Lock";
+ Typography,
+} from "@mui/material";
+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}`);
});
}
};
@@ -217,7 +205,9 @@ const LoginPage = ({ theme }) => {
_ => {
if (
!formData.base_url ||
- !formData.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+$/)
+ !formData.base_url.match(
+ /^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/
+ )
)
return;
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
@@ -263,111 +253,113 @@ const LoginPage = ({ theme }) => {
);
return (
-
-
+ <>
+
-
-
-
+
+
-
-
{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 39e28c8..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 "@material-ui/core";
-import { MenuItemLink, getResources } from "react-admin";
-import DefaultIcon from "@material-ui/icons/ViewList";
-import LabelIcon from "@material-ui/icons/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 feb5e71..2b338a6 100644
--- a/src/components/RoomDirectory.js
+++ b/src/components/RoomDirectory.js
@@ -1,41 +1,35 @@
-import React, { Fragment } from "react";
-import Avatar from "@material-ui/core/Avatar";
-import { Chip } from "@material-ui/core";
-import { connect } from "react-redux";
-import FolderSharedIcon from "@material-ui/icons/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 (
@@ -51,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",
}),
@@ -132,131 +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 translate = useTranslate();
- 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 1117321..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,20 +7,24 @@ import {
Toolbar,
required,
useCreate,
- useMutation,
+ useDataProvider,
+ useListContext,
useNotify,
useRecordContext,
useTranslate,
useUnselectAll,
} from "react-admin";
-import MessageIcon from "@material-ui/icons/Message";
-import IconCancel from "@material-ui/icons/Cancel";
-import Dialog from "@material-ui/core/Dialog";
-import DialogContent from "@material-ui/core/DialogContent";
-import DialogContentText from "@material-ui/core/DialogContentText";
-import DialogTitle from "@material-ui/core/DialogTitle";
+import { useMutation } from "react-query";
+import MessageIcon from "@mui/icons-material/Message";
+import IconCancel from "@mui/icons-material/Cancel";
+import {
+ Dialog,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+} from "@mui/material";
-const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
+const ServerNoticeDialog = ({ open, loading, onClose, onSubmit }) => {
const translate = useTranslate();
const ServerNoticeToolbar = props => (
@@ -44,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",
}),
@@ -91,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 542b89b..3fc7f78 100644
--- a/src/components/destinations.js
+++ b/src/components/destinations.js
@@ -3,7 +3,6 @@ import {
Button,
Datagrid,
DateField,
- Filter,
List,
Pagination,
ReferenceField,
@@ -20,12 +19,13 @@ import {
useRefresh,
useTranslate,
} from "react-admin";
-import AutorenewIcon from "@material-ui/icons/Autorenew";
-import FolderSharedIcon from "@material-ui/icons/FolderShared";
-import ViewListIcon from "@material-ui/icons/ViewList";
+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 7e24dff..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,
@@ -7,35 +7,17 @@ import {
useRecordContext,
useRefresh,
} from "react-admin";
-import ActionDelete from "@material-ui/icons/Delete";
-import { makeStyles } from "@material-ui/core/styles";
-import { alpha } from "@material-ui/core/styles/colorManipulator";
-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 ActionDelete from "@mui/icons-material/Delete";
+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 66340eb..cc06a0b 100644
--- a/src/components/media.js
+++ b/src/components/media.js
@@ -1,8 +1,4 @@
-import React, { Fragment, useState } from "react";
-import classnames from "classnames";
-import { alpha } from "@material-ui/core/styles/colorManipulator";
-import { makeStyles } from "@material-ui/core/styles";
-import { Tooltip } from "@material-ui/core";
+import React, { useState } from "react";
import {
BooleanInput,
Button,
@@ -18,34 +14,22 @@ import {
useRefresh,
useTranslate,
} from "react-admin";
-import BlockIcon from "@material-ui/icons/Block";
-import ClearIcon from "@material-ui/icons/Clear";
-import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
-import Dialog from "@material-ui/core/Dialog";
-import DialogContent from "@material-ui/core/DialogContent";
-import DialogContentText from "@material-ui/core/DialogContentText";
-import DialogTitle from "@material-ui/core/DialogTitle";
-import IconCancel from "@material-ui/icons/Cancel";
-import LockIcon from "@material-ui/icons/Lock";
-import LockOpenIcon from "@material-ui/icons/LockOpen";
+import BlockIcon from "@mui/icons-material/Block";
+import ClearIcon from "@mui/icons-material/Clear";
+import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
+import {
+ Dialog,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ Tooltip,
+} from "@mui/material";
+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 => {
@@ -54,19 +38,17 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
return d.getTime();
};
- const DeleteMediaToolbar = props => {
- return (
-
- }
- />
-
-
- );
- };
+ const DeleteMediaToolbar = props => (
+
+ }
+ />
+
+
+ );
return (