diff --git a/.dockerignore b/.dockerignore
index b2a9091..a9f7bc4 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,5 +1,4 @@
# Exclude a bunch of stuff which can make the build context a larger than it needs to be
-.git/
tests/
build/
lib/
diff --git a/README.md b/README.md
index 4bae8a4..7ee8b21 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
This project is built using [react-admin](https://marmelab.com/react-admin/).
-It needs at least Synapse v1.14.0 for all functions to work as expected!
+It needs at least Synapse v1.15.0 for all functions to work as expected!
## Step-By-Step install:
diff --git a/src/App.js b/src/App.js
index c8cc6aa..d92ec30 100644
--- a/src/App.js
+++ b/src/App.js
@@ -48,6 +48,7 @@ const App = () => (
icon={RoomIcon}
/>
+
);
diff --git a/src/components/devices.js b/src/components/devices.js
new file mode 100644
index 0000000..ee9292c
--- /dev/null
+++ b/src/components/devices.js
@@ -0,0 +1,89 @@
+import React, { Fragment, useState } from "react";
+import {
+ Button,
+ useMutation,
+ useNotify,
+ Confirm,
+ useRefresh,
+} from "react-admin";
+import ActionDelete from "@material-ui/icons/Delete";
+import { makeStyles } from "@material-ui/core/styles";
+import { fade } from "@material-ui/core/styles/colorManipulator";
+import classnames from "classnames";
+
+const useStyles = makeStyles(
+ theme => ({
+ deleteButton: {
+ color: theme.palette.error.main,
+ "&:hover": {
+ backgroundColor: fade(theme.palette.error.main, 0.12),
+ // Reset on mouse devices
+ "@media (hover: none)": {
+ backgroundColor: "transparent",
+ },
+ },
+ },
+ }),
+ { name: "RaDeleteDeviceButton" }
+);
+
+export const DeviceRemoveButton = props => {
+ const { record } = props;
+ const classes = useStyles(props);
+ const [open, setOpen] = useState(false);
+ const refresh = useRefresh();
+ const notify = useNotify();
+
+ const [removeDevice, { loading }] = useMutation();
+
+ if (!record) return null;
+
+ const handleClick = () => setOpen(true);
+ const handleDialogClose = () => setOpen(false);
+
+ const handleConfirm = () => {
+ removeDevice(
+ {
+ type: "delete",
+ resource: "devices",
+ payload: {
+ id: record.id,
+ user_id: record.user_id,
+ },
+ },
+ {
+ onSuccess: () => {
+ notify("resources.devices.action.erase.success");
+ refresh();
+ },
+ onFailure: () =>
+ notify("resources.devices.action.erase.failure", "error"),
+ }
+ );
+ setOpen(false);
+ };
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/components/users.js b/src/components/users.js
index 405635a..fdeb52a 100644
--- a/src/components/users.js
+++ b/src/components/users.js
@@ -2,6 +2,7 @@ import React, { cloneElement, Fragment } from "react";
import Avatar from "@material-ui/core/Avatar";
import PersonPinIcon from "@material-ui/icons/PersonPin";
import ContactMailIcon from "@material-ui/icons/ContactMail";
+import DevicesIcon from "@material-ui/icons/Devices";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import {
ArrayInput,
@@ -24,6 +25,7 @@ import {
TextInput,
SearchInput,
ReferenceField,
+ ReferenceManyField,
SelectInput,
BulkDeleteButton,
DeleteButton,
@@ -38,6 +40,7 @@ import {
} from "react-admin";
import SaveQrButton from "./SaveQrButton";
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
+import { DeviceRemoveButton } from "./devices";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles({
@@ -288,6 +291,7 @@ const UserTitle = ({ record }) => {
export const UserEdit = props => {
const classes = useStyles();
+ const translate = useTranslate();
return (
}>
}>
@@ -337,6 +341,37 @@ export const UserEdit = props => {
+ }
+ path="devices"
+ >
+
+
+
+
+
+
+
+
+
+
}
diff --git a/src/i18n/de.js b/src/i18n/de.js
index bee641b..56b2ab4 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -50,7 +50,7 @@ export default {
id: "Benutzer-ID",
name: "Name",
is_guest: "Gast",
- admin: "Admin",
+ admin: "Server Administrator",
deactivated: "Deaktiviert",
guests: "Zeige Gäste",
show_deactivated: "Zeige deaktivierte Benutzer",
@@ -64,6 +64,11 @@ export default {
address: "Adresse",
creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen",
+ // Devices:
+ device_id: "Geräte-ID",
+ display_name: "Gerätename",
+ last_seen_ts: "Zeitstempel",
+ last_seen_ip: "IP-Adresse",
},
helper: {
deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.",
@@ -122,6 +127,17 @@ export default {
user_agent: "User Agent",
},
},
+ devices: {
+ name: "Gerät |||| Geräte",
+ action: {
+ erase: {
+ title: "Entferne %{id}",
+ content: 'Möchten Sie das Gerät "%{name}" wirklich entfernen?',
+ success: "Gerät erfolgreich entfernt.",
+ failure: "Beim Entfernen ist ein Fehler aufgetreten.",
+ },
+ },
+ },
servernotices: {
name: "Serverbenachrichtigungen",
send: "Servernachricht versenden",
diff --git a/src/i18n/en.js b/src/i18n/en.js
index 84c82a8..8cb73e8 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -50,7 +50,7 @@ export default {
id: "User-ID",
name: "Name",
is_guest: "Guest",
- admin: "Admin",
+ admin: "Server Administrator",
deactivated: "Deactivated",
guests: "Show guests",
show_deactivated: "Show deactivated users",
@@ -64,6 +64,11 @@ export default {
address: "Address",
creation_ts_ms: "Creation timestamp",
consent_version: "Consent version",
+ // Devices:
+ device_id: "Device-ID",
+ display_name: "Device name",
+ last_seen_ts: "Timestamp",
+ last_seen_ip: "IP address",
},
helper: {
deactivate: "Deactivated users cannot be reactivated",
@@ -122,6 +127,17 @@ export default {
user_agent: "User agent",
},
},
+ devices: {
+ name: "Device |||| Devices",
+ action: {
+ erase: {
+ title: "Removing %{id}",
+ content: 'Are you sure you want to remove the device "%{name}"?',
+ success: "Device successfully removed.",
+ failure: "An error has occurred.",
+ },
+ },
+ },
servernotices: {
name: "Server Notices",
send: "Send server notices",
diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js
index 54c06d7..e4a3eb9 100644
--- a/src/synapse/dataProvider.js
+++ b/src/synapse/dataProvider.js
@@ -46,8 +46,8 @@ const resourceMap = {
body: data,
method: "PUT",
}),
- delete: id => ({
- endpoint: `/_synapse/admin/v1/deactivate/${id}`,
+ delete: params => ({
+ endpoint: `/_synapse/admin/v1/deactivate/${params.id}`,
body: { erase: true },
method: "POST",
}),
@@ -90,6 +90,19 @@ const resourceMap = {
method: "POST",
}),
},
+ devices: {
+ map: d => ({
+ ...d,
+ id: d.device_id,
+ }),
+ data: "devices",
+ reference: id => ({
+ endpoint: `/_synapse/admin/v2/users/${id}/devices`,
+ }),
+ delete: params => ({
+ endpoint: `/_synapse/admin/v2/users/${params.user_id}/devices/${params.id}`,
+ }),
+ },
connections: {
path: "/_synapse/admin/v1/whois",
map: c => ({
@@ -189,30 +202,18 @@ const dataProvider = {
},
getManyReference: (resource, params) => {
- // FIXME
console.log("getManyReference " + resource);
- const { page, perPage } = params.pagination;
- const { field, order } = params.sort;
- const query = {
- sort: JSON.stringify([field, order]),
- range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
- filter: JSON.stringify({
- ...params.filter,
- [params.target]: params.id,
- }),
- };
const homeserver = localStorage.getItem("base_url");
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
const res = resourceMap[resource];
- const endpoint_url = homeserver + res.path;
- const url = `${endpoint_url}?${stringify(query)}`;
+ const ref = res["reference"](params.id);
+ const endpoint_url = homeserver + ref.endpoint;
- return jsonClient(url).then(({ headers, json }) => ({
- data: json,
- total: parseInt(headers.get("content-range").split("/").pop(), 10),
+ return jsonClient(endpoint_url).then(({ headers, json }) => ({
+ data: json[res.data].map(res.map),
}));
},
@@ -299,11 +300,11 @@ const dataProvider = {
const res = resourceMap[resource];
if ("delete" in res) {
- const del = res["delete"](params.id);
+ const del = res["delete"](params);
const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, {
- method: del.method,
- body: JSON.stringify(del.body),
+ method: "method" in del ? del.method : "DELETE",
+ body: "body" in del ? JSON.stringify(del.body) : null,
}).then(({ json }) => ({
data: json,
}));
@@ -328,11 +329,11 @@ const dataProvider = {
if ("delete" in res) {
return Promise.all(
params.ids.map(id => {
- const del = res["delete"](id);
+ const del = res["delete"]({ ...params, id: id });
const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, {
- method: del.method,
- body: JSON.stringify(del.body),
+ method: "method" in del ? del.method : "DELETE",
+ body: "body" in del ? JSON.stringify(del.body) : null,
});
})
).then(responses => ({