From 352ab1290a60000012270d87df24ab00203f02d1 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Wed, 8 Jul 2020 10:58:57 +0200 Subject: [PATCH 1/5] Add devices tab to UserEdit component Allows to view user devices. API was added by synapse v1.15.0. Change-Id: Id0693bf6cd6f6182c657412cf8036537e2db9df7 --- README.md | 2 +- src/App.js | 1 + src/components/users.js | 33 +++++++++++++++++++++++++++++++++ src/i18n/de.js | 9 +++++++++ src/i18n/en.js | 9 +++++++++ src/synapse/dataProvider.js | 30 ++++++++++++++---------------- 6 files changed, 67 insertions(+), 17 deletions(-) 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 b3c40c3..a7fc125 100644 --- a/src/App.js +++ b/src/App.js @@ -37,6 +37,7 @@ const App = () => ( /> + ); diff --git a/src/components/users.js b/src/components/users.js index c65cd12..cb98cd9 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, @@ -23,6 +24,7 @@ import { TextField, TextInput, ReferenceField, + ReferenceManyField, SelectInput, BulkDeleteButton, DeleteButton, @@ -205,6 +207,7 @@ const UserTitle = ({ record }) => { }; export const UserEdit = props => { const classes = useStyles(); + const translate = useTranslate(); return ( }> }> @@ -254,6 +257,36 @@ export const UserEdit = props => { + } + path="devices" + > + + + + + + + + + } diff --git a/src/i18n/de.js b/src/i18n/de.js index 560e310..386fdd6 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -107,6 +107,15 @@ 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", + }, + }, servernotices: { name: "Serverbenachrichtigungen", send: "Servernachricht versenden", diff --git a/src/i18n/en.js b/src/i18n/en.js index 12ba1ae..219f539 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -105,6 +105,15 @@ 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", + }, + }, servernotices: { name: "Server Notices", send: "Send server notices", diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js index cebc413..674d935 100644 --- a/src/synapse/dataProvider.js +++ b/src/synapse/dataProvider.js @@ -67,6 +67,16 @@ const resourceMap = { return json.total_rooms; }, }, + devices: { + map: d => ({ + ...d, + id: d.device_id, + }), + data: "devices", + reference: id => ({ + endpoint: `/_synapse/admin/v2/users/${id}/devices`, + }), + }, connections: { path: "/_synapse/admin/v1/whois", map: c => ({ @@ -166,30 +176,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), })); }, From 1074178e31e3e5d1c3c8df2c55199bff7bb061a0 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Tue, 21 Jul 2020 11:52:13 +0200 Subject: [PATCH 2/5] Rename Admin -> Server Administrator Change-Id: Ic11539252af553dbb7cca37996f2402669a9c0e4 --- src/i18n/de.js | 2 +- src/i18n/en.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/de.js b/src/i18n/de.js index 386fdd6..f1ec58f 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -37,7 +37,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", diff --git a/src/i18n/en.js b/src/i18n/en.js index 219f539..b95b896 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -35,7 +35,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", From 78e7c5f391222b6d35904197891a94ac3bea39d3 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Thu, 23 Jul 2020 09:24:18 +0200 Subject: [PATCH 3/5] Fix translation of user devices Fixes #58. Change-Id: Ic2f91917310fd1ba59636d06c81c338ca9dd297e --- src/i18n/de.js | 11 +++++------ src/i18n/en.js | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/i18n/de.js b/src/i18n/de.js index f1ec58f..3f95a1d 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -51,6 +51,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.", @@ -109,12 +114,6 @@ export default { }, devices: { name: "Gerät |||| Geräte", - fields: { - device_id: "Geräte-ID", - display_name: "Anzeigename", - last_seen_ts: "Zeitstempel", - last_seen_ip: "IP-Adresse", - }, }, servernotices: { name: "Serverbenachrichtigungen", diff --git a/src/i18n/en.js b/src/i18n/en.js index b95b896..cc98c6b 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -49,6 +49,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", @@ -107,12 +112,6 @@ export default { }, devices: { name: "Device |||| Devices", - fields: { - device_id: "Device-ID", - display_name: "Displayname", - last_seen_ts: "Timestamp", - last_seen_ip: "IP address", - }, }, servernotices: { name: "Server Notices", From 314906657f4e69f0d5e2d776cb65912208e74af2 Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Wed, 8 Jul 2020 11:11:24 +0200 Subject: [PATCH 4/5] Add support to remove user devices (#57) Change-Id: I19176daa656b9280ccd00f1ca0095e72870ca21e --- src/components/devices.js | 89 +++++++++++++++++++++++++++++++++++++ src/components/users.js | 2 + src/i18n/de.js | 8 ++++ src/i18n/en.js | 8 ++++ src/synapse/dataProvider.js | 19 ++++---- 5 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 src/components/devices.js 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 cb98cd9..a6889b2 100644 --- a/src/components/users.js +++ b/src/components/users.js @@ -38,6 +38,7 @@ import { sanitizeListRestProps, } from "react-admin"; import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices"; +import { DeviceRemoveButton } from "./devices"; import { makeStyles } from "@material-ui/core/styles"; const useStyles = makeStyles({ @@ -284,6 +285,7 @@ export const UserEdit = props => { }} sortable={false} /> + diff --git a/src/i18n/de.js b/src/i18n/de.js index 3f95a1d..0243041 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -114,6 +114,14 @@ export default { }, 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", diff --git a/src/i18n/en.js b/src/i18n/en.js index cc98c6b..f6501c9 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -112,6 +112,14 @@ export default { }, 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", diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js index 674d935..d4c8bcd 100644 --- a/src/synapse/dataProvider.js +++ b/src/synapse/dataProvider.js @@ -45,8 +45,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", }), @@ -76,6 +76,9 @@ const resourceMap = { 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", @@ -274,11 +277,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, })); @@ -303,11 +306,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 => ({ From bbbca0c57c3b004c31f6d02f72d6cae198a2309e Mon Sep 17 00:00:00 2001 From: Manuel Stahl Date: Tue, 28 Jul 2020 10:29:37 +0200 Subject: [PATCH 5/5] Add git context to docker, so we can derive the release version "git describe --tags" requires the git context. Change-Id: I2bc5dde056c2ac480513004fb99336397355af30 --- .dockerignore | 1 - 1 file changed, 1 deletion(-) 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/