diff --git a/src/App.js b/src/App.js
index a70176a..ccc3e5e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -4,7 +4,7 @@ import polyglotI18nProvider from "ra-i18n-polyglot";
import authProvider from "./synapse/authProvider";
import dataProvider from "./synapse/dataProvider";
import { UserList, UserCreate, UserEdit } from "./components/users";
-import { RoomList, RoomCreate, RoomShow } from "./components/rooms";
+import { RoomList, RoomCreate, RoomShow, RoomEdit } from "./components/rooms";
import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group";
import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList";
@@ -45,6 +45,7 @@ const App = () => (
list={RoomList}
create={RoomCreate}
show={RoomShow}
+ edit={RoomEdit}
icon={RoomIcon}
/>
diff --git a/src/components/rooms.js b/src/components/rooms.js
index 9484ac7..bfd3921 100644
--- a/src/components/rooms.js
+++ b/src/components/rooms.js
@@ -1,10 +1,14 @@
import React from "react";
import { connect } from "react-redux";
+import { Route, Link } from "react-router-dom";
import {
AutocompleteArrayInput,
+ AutocompleteInput,
BooleanInput,
BooleanField,
+ Button,
Create,
+ Edit,
Datagrid,
Filter,
FormTab,
@@ -12,24 +16,39 @@ import {
Pagination,
ReferenceArrayInput,
ReferenceField,
+ ReferenceInput,
ReferenceManyField,
SelectField,
Show,
+ SimpleForm,
Tab,
TabbedForm,
TabbedShowLayout,
TextField,
TextInput,
+ Toolbar,
+ useDataProvider,
+ useRefresh,
useTranslate,
} from "react-admin";
import get from "lodash/get";
-import { Tooltip, Typography, Chip } from "@material-ui/core";
+import {
+ Tooltip,
+ Typography,
+ Chip,
+ Drawer,
+ styled,
+ withStyles,
+ Select,
+ MenuItem,
+} from "@material-ui/core";
import HttpsIcon from "@material-ui/icons/Https";
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
import PageviewIcon from "@material-ui/icons/Pageview";
import UserIcon from "@material-ui/icons/Group";
import ViewListIcon from "@material-ui/icons/ViewList";
import VisibilityIcon from "@material-ui/icons/Visibility";
+import ContentSave from "@material-ui/icons/Save";
const RoomPagination = props => (
@@ -61,12 +80,13 @@ const EncryptionField = ({ source, record = {}, emptyText }) => {
);
};
-const validateDisplayName = fieldval =>
- fieldval === undefined
+const validateDisplayName = fieldval => {
+ return fieldval == null
? "synapseadmin.rooms.room_name_required"
: fieldval.length === 0
? "synapseadmin.rooms.room_name_required"
: undefined;
+};
function approximateAliasLength(alias, homeserver) {
/* TODO maybe handle punycode in homeserver name */
@@ -141,6 +161,16 @@ export const RoomCreate = props => (
validate={validateAlias}
placeholder="#"
/>
+ ({ user_id: searchText })}
+ >
+
+
{
);
};
+// Explicitely passing "to" prop
+// Toolbar adds all kinds of unsupported props to its children :(
+const StyledLink = styles => {
+ const Styled = styled(Link)(styles);
+ return ({ to, children }) => {children};
+};
+
+const RoomMemberEditToolbar = ({ backLink, translate, onSave, ...props }) => {
+ const SaveLink = StyledLink({
+ textDecoration: "none",
+ });
+ const CancelLink = StyledLink({
+ textDecoration: "none",
+ marginLeft: "1em",
+ });
+ const SaveIcon = styled(ContentSave)({
+ width: "1rem",
+ marginRight: "0.25em",
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+const RoomMemberIdField = ({ memberId, data = {} }) => {
+ const value = get(data[memberId], "id");
+
+ return (
+
+ {value}
+
+ );
+};
+
+const RoomMemberRoleInput = ({ memberId, data = {}, translate, onChange }) => {
+ const roleValue = get(data[memberId], "role");
+ const [role, setRole] = React.useState(roleValue);
+
+ React.useEffect(() => {
+ onChange(roleValue);
+ }, [onChange, roleValue]);
+
+ return (
+
+
+
+ );
+};
+
+const RoomMemberEdit = ({ backLink, memberId, ...props }) => {
+ const translate = useTranslate();
+ const refresh = useRefresh();
+ const dataProvider = useDataProvider();
+
+ const [role, setRole] = React.useState();
+
+ const { id } = props;
+
+ return (
+
+ {
+ dataProvider
+ .update("rooms", {
+ data: {
+ id,
+ member_roles: [{ member_id: memberId, role }],
+ },
+ })
+ .then(() => {
+ refresh();
+ });
+ }}
+ />
+ }
+ >
+
+
+
+
+
+
+
+
+ );
+};
+
+const drawerStyles = {
+ paper: {
+ width: 300,
+ },
+};
+const StyledDrawer = withStyles(drawerStyles)(({ classes, ...props }) => (
+
+));
+
+export const RoomEdit = props => {
+ const translate = useTranslate();
+
+ return (
+
+ }>
+
+ }>
+ ({ user_id: searchText })}
+ >
+
+
+
+
+
+ `/rooms/${encodeURIComponent(
+ record.parentId
+ )}/${encodeURIComponent(id)}`
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {({ match }) => {
+ const isMatch = !!match && !!match.params;
+
+ return (
+
+ {isMatch ? (
+
+ ) : (
+
+ )}
+
+ );
+ }}
+
+
+ );
+};
+
export const RoomShow = props => {
const translate = useTranslate();
return (
@@ -227,6 +493,18 @@ export const RoomShow = props => {
>
+
diff --git a/src/i18n/de.js b/src/i18n/de.js
index 085dcfa..a47afd5 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -70,6 +70,7 @@ export default {
display_name: "Gerätename",
last_seen_ts: "Zeitstempel",
last_seen_ip: "IP-Adresse",
+ role: "Rolle",
},
type: {
default: "Standard",
@@ -83,6 +84,11 @@ export default {
action: {
erase: "Lösche Benutzerdaten",
},
+ roles: {
+ user: "Nutzer",
+ mod: "Moderator",
+ admin: "Administrator",
+ },
},
rooms: {
name: "Raum |||| Räume",
diff --git a/src/i18n/en.js b/src/i18n/en.js
index 0c4ee44..e648dd5 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -69,6 +69,7 @@ export default {
display_name: "Device name",
last_seen_ts: "Timestamp",
last_seen_ip: "IP address",
+ role: "Role",
},
type: {
default: "Standard",
@@ -82,6 +83,11 @@ export default {
action: {
erase: "Erase user data",
},
+ roles: {
+ user: "User",
+ mod: "Moderator",
+ admin: "Administrator",
+ },
},
rooms: {
name: "Room |||| Rooms",
diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js
index bdff9b7..1a71196 100644
--- a/src/synapse/dataProvider.js
+++ b/src/synapse/dataProvider.js
@@ -25,6 +25,16 @@ const mxcUrlToHttp = mxcUrl => {
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
};
+const powerLevelToRole = powerLevel =>
+ powerLevel < 100 ? (powerLevel < 50 ? "user" : "mod") : "admin";
+
+const POWER_LEVELS = {
+ admin: 100,
+ mod: 50,
+ user: 0,
+};
+const roleToPowerLevel = role => POWER_LEVELS[role] || 0;
+
const resourceMap = {
users: {
path: "/_synapse/admin/v2/users",
@@ -66,8 +76,9 @@ const resourceMap = {
data: "rooms",
total: json => json.total_rooms,
create: data => ({
- endpoint: "/_matrix/client/r0/createRoom",
+ endpoint: "/_synapse/admin/v1/rooms",
body: {
+ owner: data.owner,
name: data.name,
room_alias_name: data.canonical_alias,
visibility: data.public ? "public" : "private",
@@ -89,6 +100,20 @@ const resourceMap = {
},
method: "POST",
}),
+ delete: params => ({
+ endpoint: `/_synapse/admin/v1/rooms/${params.id}/delete`,
+ body: { erase: true },
+ method: "POST",
+ }),
+ transformBeforeUpdate: data => {
+ return {
+ ...data,
+ member_roles: (data.member_roles || []).map(member => ({
+ member_id: member.member_id,
+ power_level: roleToPowerLevel(member.role),
+ })),
+ };
+ },
},
devices: {
map: d => ({
@@ -113,10 +138,11 @@ const resourceMap = {
},
room_members: {
map: m => ({
- id: m,
+ role: powerLevelToRole(m.power_level),
+ id: m.user_id,
}),
reference: id => ({
- endpoint: `/_synapse/admin/v1/rooms/${id}/members`,
+ endpoint: `/_synapse/admin/v1/rooms/${id}/power_levels`,
}),
data: "members",
},
@@ -183,7 +209,7 @@ const dataProvider = {
},
getOne: (resource, params) => {
- console.log("getOne " + resource);
+ console.log("getOne " + resource, params);
const homeserver = localStorage.getItem("base_url");
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
@@ -222,7 +248,10 @@ const dataProvider = {
const endpoint_url = homeserver + ref.endpoint;
return jsonClient(endpoint_url).then(({ headers, json }) => ({
- data: json[res.data].map(res.map),
+ data: json[res.data].map(res.map).map(element => ({
+ ...element,
+ parentId: params.id,
+ })),
}));
},
@@ -233,10 +262,13 @@ const dataProvider = {
const res = resourceMap[resource];
+ const transform = res.transformBeforeUpdate || (x => x);
+ const data = transform(params.data);
+
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.data.id}`, {
method: "PUT",
- body: JSON.stringify(params.data, filterNullValues),
+ body: JSON.stringify(data, filterNullValues),
}).then(({ json }) => ({
data: res.map(json),
}));