diff --git a/src/App.js b/src/App.js
index f943e3f..ed78de1 100644
--- a/src/App.js
+++ b/src/App.js
@@ -37,6 +37,7 @@ const App = () => (
/>
+
);
diff --git a/src/components/devices.js b/src/components/devices.js
new file mode 100644
index 0000000..196f3d8
--- /dev/null
+++ b/src/components/devices.js
@@ -0,0 +1,96 @@
+import React, { Fragment, useState } from "react";
+import {
+ Button,
+ useMutation,
+ useNotify,
+ useTranslate,
+ 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";
+
+export const RemoveDeviceButton = props => {
+ const { record } = props;
+ const classes = useStyles(props);
+ const [open, setOpen] = useState(false);
+ const translate = useTranslate();
+ const refresh = useRefresh();
+ const notify = useNotify();
+
+ const [removeDevice, { loading }] = useMutation();
+ const handleSend = values => {
+ removeDevice(
+ {
+ type: "removeDevice",
+ resource: "devices",
+ payload: {
+ user_id: record.user_id,
+ device_id: record.device_id,
+ },
+ },
+ {
+ onSuccess: () => {
+ notify("resources.devices.action.remove_success");
+ refresh();
+ },
+ onFailure: () =>
+ notify("resources.devices.action.remove_failure", "error"),
+ }
+ );
+ };
+
+ const handleClick = () => setOpen(true);
+ const handleDialogClose = () => setOpen(false);
+
+ const handleConfirm = () => {
+ handleSend();
+ setOpen(false);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+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" }
+);
diff --git a/src/components/users.js b/src/components/users.js
index ff21436..7ae40a1 100644
--- a/src/components/users.js
+++ b/src/components/users.js
@@ -1,6 +1,7 @@
import React, { Fragment } from "react";
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,
@@ -32,6 +33,7 @@ import {
Pagination,
} from "react-admin";
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
+import { RemoveDeviceButton } from "./devices";
const UserPagination = props => (
@@ -148,89 +150,129 @@ const UserTitle = ({ record }) => {
);
};
-export const UserEdit = props => (
- }>
- }>
- }>
-
-
-
-
-
-
-
-
- }
- path="threepid"
- >
-
-
-
-
-
-
-
- }
- path="connections"
- >
- {
+ const translate = useTranslate();
+ return (
+ }>
+ }>
+ }
>
-
+
+
+
+
+
+
+
+ }
+ path="threepid"
+ >
+
+
+
+
+
+
+
+ }
+ path="devices"
+ >
+
-
-
-
-
-
-
-
-
-
-
-);
+
+
+
+
+
+
+
+
+
+
+
+ }
+ path="connections"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/i18n/de.js b/src/i18n/de.js
index 5ec9947..066bc64 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -73,6 +73,22 @@ export default {
user_agent: "User Agent",
},
},
+ devices: {
+ name: "Gerät |||| Geräte",
+ fields: {
+ device_id: "Geräte-ID",
+ display_name: "Anzeigename",
+ last_seen_ts: "Zeitstempel",
+ last_seen_ip: "IP-Adresse",
+ },
+ action: {
+ remove_title: "Entferne %{name}: %{id}",
+ remove_content:
+ "Möchten Sie dieses %{name} wirklich entfernen? %{display_name}: %{displayname}",
+ remove_success: "Gerät erfolgreich entfernt.",
+ remove_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 2b29701..88b4b29 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -72,6 +72,22 @@ export default {
user_agent: "User agent",
},
},
+ devices: {
+ name: "Device |||| Devices",
+ fields: {
+ device_id: "Device-ID",
+ display_name: "Displayname",
+ last_seen_ts: "Timestamp",
+ last_seen_ip: "IP address",
+ },
+ action: {
+ remove_title: "Remove %{name} #%{id}",
+ remove_content:
+ "Are you sure you want to remove this %{name}? %{display_name}: %{displayname}",
+ remove_success: "Device successfully removed.",
+ remove_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 21748fd..d728d4f 100644
--- a/src/synapse/dataProvider.js
+++ b/src/synapse/dataProvider.js
@@ -32,6 +32,9 @@ const resourceMap = {
? parseInt(json.next_token, 10) + perPage
: from + json.users.length;
},
+ getMany: id => ({
+ endpoint: `/_synapse/admin/v2/users/${id}`,
+ }),
create: data => ({
endpoint: `/_synapse/admin/v2/users/${data.id}`,
body: data,
@@ -59,13 +62,26 @@ const resourceMap = {
return json.total_rooms;
},
},
+ devices: {
+ path: "/_synapse/admin/v2/users",
+ map: d => ({
+ ...d,
+ id: d.devices[0].user_id,
+ }),
+ data: "devices",
+ getMany: id => ({
+ endpoint: `/_synapse/admin/v2/users/${id}/devices`,
+ }),
+ },
connections: {
- path: "/_synapse/admin/v1/whois",
map: c => ({
...c,
id: c.user_id,
}),
data: "connections",
+ getMany: id => ({
+ endpoint: `/_synapse/admin/v1/whois/${id}`,
+ }),
},
servernotices: {
map: n => ({ id: n.event_id }),
@@ -148,10 +164,14 @@ const dataProvider = {
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
const res = resourceMap[resource];
+ if (!("getMany" in res)) return Promise.reject();
- const endpoint_url = homeserver + res.path;
return Promise.all(
- params.ids.map(id => jsonClient(`${endpoint_url}/${id}`))
+ params.ids.map(id => {
+ const getMany = res["getMany"](id);
+ const endpoint_url = homeserver + getMany.endpoint;
+ return jsonClient(endpoint_url);
+ })
).then(responses => ({
data: responses.map(({ json }) => res.map(json)),
}));
@@ -321,6 +341,24 @@ const dataProvider = {
}));
}
},
+
+ removeDevice: (resource, params) => {
+ console.log("removeDevice " + resource);
+ const homeserver = localStorage.getItem("base_url");
+ if (!homeserver || !(resource in resourceMap)) return Promise.reject();
+
+ const res = resourceMap[resource];
+ const endpoint_url = homeserver + res.path;
+
+ return jsonClient(
+ `${endpoint_url}/${params.user_id}/devices/${params.device_id}`,
+ {
+ method: "DELETE",
+ }
+ ).then(({ json }) => ({
+ data: json,
+ }));
+ },
};
export default dataProvider;