Merge tag '0.4.2' into amp.chat

Change-Id: Id12309f0a4d3ff9983325e69131d5eebe5bd0bde
This commit is contained in:
Manuel Stahl 2020-07-30 12:56:20 +02:00
commit dd00a76603
8 changed files with 185 additions and 28 deletions

View File

@ -1,5 +1,4 @@
# Exclude a bunch of stuff which can make the build context a larger than it needs to be # Exclude a bunch of stuff which can make the build context a larger than it needs to be
.git/
tests/ tests/
build/ build/
lib/ lib/

View File

@ -4,7 +4,7 @@
This project is built using [react-admin](https://marmelab.com/react-admin/). 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: ## Step-By-Step install:

View File

@ -48,6 +48,7 @@ const App = () => (
icon={RoomIcon} icon={RoomIcon}
/> />
<Resource name="connections" /> <Resource name="connections" />
<Resource name="devices" />
<Resource name="servernotices" /> <Resource name="servernotices" />
</Admin> </Admin>
); );

89
src/components/devices.js Normal file
View File

@ -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 (
<Fragment>
<Button
label="ra.action.remove"
onClick={handleClick}
className={classnames("ra-delete-button", classes.deleteButton)}
>
<ActionDelete />
</Button>
<Confirm
isOpen={open}
loading={loading}
onConfirm={handleConfirm}
onClose={handleDialogClose}
title="resources.devices.action.erase.title"
content="resources.devices.action.erase.content"
translateOptions={{
id: record.id,
name: record.display_name ? record.display_name : record.id,
}}
/>
</Fragment>
);
};

View File

@ -2,6 +2,7 @@ import React, { cloneElement, Fragment } from "react";
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@material-ui/core/Avatar";
import PersonPinIcon from "@material-ui/icons/PersonPin"; import PersonPinIcon from "@material-ui/icons/PersonPin";
import ContactMailIcon from "@material-ui/icons/ContactMail"; import ContactMailIcon from "@material-ui/icons/ContactMail";
import DevicesIcon from "@material-ui/icons/Devices";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent"; import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import { import {
ArrayInput, ArrayInput,
@ -24,6 +25,7 @@ import {
TextInput, TextInput,
SearchInput, SearchInput,
ReferenceField, ReferenceField,
ReferenceManyField,
SelectInput, SelectInput,
BulkDeleteButton, BulkDeleteButton,
DeleteButton, DeleteButton,
@ -38,6 +40,7 @@ import {
} from "react-admin"; } from "react-admin";
import SaveQrButton from "./SaveQrButton"; import SaveQrButton from "./SaveQrButton";
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices"; import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
import { DeviceRemoveButton } from "./devices";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles({ const useStyles = makeStyles({
@ -288,6 +291,7 @@ const UserTitle = ({ record }) => {
export const UserEdit = props => { export const UserEdit = props => {
const classes = useStyles(); const classes = useStyles();
const translate = useTranslate();
return ( return (
<Edit {...props} title={<UserTitle />}> <Edit {...props} title={<UserTitle />}>
<TabbedForm toolbar={<UserEditToolbar />}> <TabbedForm toolbar={<UserEditToolbar />}>
@ -337,6 +341,37 @@ export const UserEdit = props => {
</SimpleFormIterator> </SimpleFormIterator>
</ArrayInput> </ArrayInput>
</FormTab> </FormTab>
<FormTab
label={translate("resources.devices.name", { smart_count: 2 })}
icon={<DevicesIcon />}
path="devices"
>
<ReferenceManyField
reference="devices"
target="user_id"
addLabel={false}
>
<Datagrid style={{ width: "100%" }}>
<TextField source="device_id" sortable={false} />
<TextField source="display_name" sortable={false} />
<TextField source="last_seen_ip" sortable={false} />
<DateField
source="last_seen_ts"
showTime
options={{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}}
sortable={false}
/>
<DeviceRemoveButton />
</Datagrid>
</ReferenceManyField>
</FormTab>
<FormTab <FormTab
label="resources.connections.name" label="resources.connections.name"
icon={<SettingsInputComponentIcon />} icon={<SettingsInputComponentIcon />}

View File

@ -50,7 +50,7 @@ export default {
id: "Benutzer-ID", id: "Benutzer-ID",
name: "Name", name: "Name",
is_guest: "Gast", is_guest: "Gast",
admin: "Admin", admin: "Server Administrator",
deactivated: "Deaktiviert", deactivated: "Deaktiviert",
guests: "Zeige Gäste", guests: "Zeige Gäste",
show_deactivated: "Zeige deaktivierte Benutzer", show_deactivated: "Zeige deaktivierte Benutzer",
@ -64,6 +64,11 @@ export default {
address: "Adresse", address: "Adresse",
creation_ts_ms: "Zeitpunkt der Erstellung", creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen", 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: { helper: {
deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.", deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.",
@ -122,6 +127,17 @@ export default {
user_agent: "User Agent", 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: { servernotices: {
name: "Serverbenachrichtigungen", name: "Serverbenachrichtigungen",
send: "Servernachricht versenden", send: "Servernachricht versenden",

View File

@ -50,7 +50,7 @@ export default {
id: "User-ID", id: "User-ID",
name: "Name", name: "Name",
is_guest: "Guest", is_guest: "Guest",
admin: "Admin", admin: "Server Administrator",
deactivated: "Deactivated", deactivated: "Deactivated",
guests: "Show guests", guests: "Show guests",
show_deactivated: "Show deactivated users", show_deactivated: "Show deactivated users",
@ -64,6 +64,11 @@ export default {
address: "Address", address: "Address",
creation_ts_ms: "Creation timestamp", creation_ts_ms: "Creation timestamp",
consent_version: "Consent version", consent_version: "Consent version",
// Devices:
device_id: "Device-ID",
display_name: "Device name",
last_seen_ts: "Timestamp",
last_seen_ip: "IP address",
}, },
helper: { helper: {
deactivate: "Deactivated users cannot be reactivated", deactivate: "Deactivated users cannot be reactivated",
@ -122,6 +127,17 @@ export default {
user_agent: "User agent", 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: { servernotices: {
name: "Server Notices", name: "Server Notices",
send: "Send server notices", send: "Send server notices",

View File

@ -46,8 +46,8 @@ const resourceMap = {
body: data, body: data,
method: "PUT", method: "PUT",
}), }),
delete: id => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/deactivate/${id}`, endpoint: `/_synapse/admin/v1/deactivate/${params.id}`,
body: { erase: true }, body: { erase: true },
method: "POST", method: "POST",
}), }),
@ -90,6 +90,19 @@ const resourceMap = {
method: "POST", 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: { connections: {
path: "/_synapse/admin/v1/whois", path: "/_synapse/admin/v1/whois",
map: c => ({ map: c => ({
@ -189,30 +202,18 @@ const dataProvider = {
}, },
getManyReference: (resource, params) => { getManyReference: (resource, params) => {
// FIXME
console.log("getManyReference " + resource); 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"); const homeserver = localStorage.getItem("base_url");
if (!homeserver || !(resource in resourceMap)) return Promise.reject(); if (!homeserver || !(resource in resourceMap)) return Promise.reject();
const res = resourceMap[resource]; const res = resourceMap[resource];
const endpoint_url = homeserver + res.path; const ref = res["reference"](params.id);
const url = `${endpoint_url}?${stringify(query)}`; const endpoint_url = homeserver + ref.endpoint;
return jsonClient(url).then(({ headers, json }) => ({ return jsonClient(endpoint_url).then(({ headers, json }) => ({
data: json, data: json[res.data].map(res.map),
total: parseInt(headers.get("content-range").split("/").pop(), 10),
})); }));
}, },
@ -299,11 +300,11 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
if ("delete" in res) { if ("delete" in res) {
const del = res["delete"](params.id); const del = res["delete"](params);
const endpoint_url = homeserver + del.endpoint; const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, { return jsonClient(endpoint_url, {
method: del.method, method: "method" in del ? del.method : "DELETE",
body: JSON.stringify(del.body), body: "body" in del ? JSON.stringify(del.body) : null,
}).then(({ json }) => ({ }).then(({ json }) => ({
data: json, data: json,
})); }));
@ -328,11 +329,11 @@ const dataProvider = {
if ("delete" in res) { if ("delete" in res) {
return Promise.all( return Promise.all(
params.ids.map(id => { params.ids.map(id => {
const del = res["delete"](id); const del = res["delete"]({ ...params, id: id });
const endpoint_url = homeserver + del.endpoint; const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, { return jsonClient(endpoint_url, {
method: del.method, method: "method" in del ? del.method : "DELETE",
body: JSON.stringify(del.body), body: "body" in del ? JSON.stringify(del.body) : null,
}); });
}) })
).then(responses => ({ ).then(responses => ({