Merge tag '0.4.2' into amp.chat
Change-Id: Id12309f0a4d3ff9983325e69131d5eebe5bd0bde
This commit is contained in:
commit
dd00a76603
@ -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/
|
||||
|
@ -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:
|
||||
|
||||
|
@ -48,6 +48,7 @@ const App = () => (
|
||||
icon={RoomIcon}
|
||||
/>
|
||||
<Resource name="connections" />
|
||||
<Resource name="devices" />
|
||||
<Resource name="servernotices" />
|
||||
</Admin>
|
||||
);
|
||||
|
89
src/components/devices.js
Normal file
89
src/components/devices.js
Normal 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>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<Edit {...props} title={<UserTitle />}>
|
||||
<TabbedForm toolbar={<UserEditToolbar />}>
|
||||
@ -337,6 +341,37 @@ export const UserEdit = props => {
|
||||
</SimpleFormIterator>
|
||||
</ArrayInput>
|
||||
</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
|
||||
label="resources.connections.name"
|
||||
icon={<SettingsInputComponentIcon />}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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 => ({
|
||||
|
Loading…
Reference in New Issue
Block a user