Compare commits

...

16 Commits

Author SHA1 Message Date
Manuel Stahl ac0657c428 Add hint about required synapse version
Change-Id: I8152681d695ded78d0f9b9d82bc038aa67723fbc
2020-07-08 09:55:52 +02:00
Michael Albert ab709aee3e Add screenshots and install instructions
Change-Id: Ibfede12d700924b7b3b01af5f3a3624f21a93862
2020-07-08 07:49:42 +00:00
dklimpel 6da3c8b885 Bugfix translation of plural in UserTitle 2020-07-08 08:50:00 +02:00
Manuel Stahl ab04db5baf Get avatar_url and displayname from v2/users API
API was added by synapse v1.13.0.

Change-Id: I927b81882fa20e5b3de3d9fc216e2136f7036bba
2020-07-07 18:51:35 +02:00
Dirk Klimpel 8282a3caf8 Move threepids in UserEdit to a separate tab (#51)
Separates information into individual tabs for a better overview.
2020-07-06 12:35:26 +02:00
Michael Albert 2fc75cd6fc Merge pull request #43 from dklimpel/extend_room_list
Extend the room list with further attributes
2020-07-03 19:32:00 +02:00
Dirk Klimpel 3fd615943c Shows encrypted status with icons 2020-07-01 22:36:15 +02:00
dklimpel aaf1ebb909 Change field creation_ts * 1000 to creation_ts_ms 2020-06-16 10:15:22 +02:00
dklimpel 627f3d2917 Add creation timestamp and consent version to UserEdit
Add information about the user to UserEdit
- creation timestamp
- consent version
2020-06-16 10:15:22 +02:00
dklimpel 168e249296 Bugfix removes the ability to click on individual connections.
If you click on a connection in UserEdit, you will get an empty page.
This solves the problem.
2020-06-16 09:45:43 +02:00
dklimpel 5bdfb80db7 Bugfix sort users by user_id
Users are not sortable by `user_id`.
Set `sortable={false}`.
2020-06-10 13:11:35 +02:00
dependabot[bot] b7c3684b80 Bump websocket-extensions from 0.1.3 to 0.1.4 (#49)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-09 14:50:58 +02:00
Manuel Stahl 0ada5287d7 Show Synapse server version on login page
Change-Id: Id9b0d2adf83590524069d308f4fe9e5d14147295
2020-05-25 13:29:15 +02:00
dklimpel 3884c50012 Extend the room list with further attributes
Add further attributes:
- is_encrypted
- federatable
- public
- state_events
- version
- joined_local_members

Also add the ability to sort.

API was added by synapse v1.13.0.
2020-05-23 17:43:33 +02:00
Michael Albert 300e22a537 Show version of Synapse-Admin
Change-Id: I354e3f3b3e4f45e5ca72318ce70b66bee433f3d6
2020-05-15 13:28:51 +02:00
Dirk Klimpel 009ce803e2 Add ServerNoticeButton to UserBulkActionButtons (#41)
This adds the button to send "Server Notices" to many users at once.
2020-05-06 09:03:33 +02:00
12 changed files with 389 additions and 114 deletions
+24 -2
View File
@@ -4,6 +4,28 @@
This project is built using [react-admin](https://marmelab.com/react-admin/). This project is built using [react-admin](https://marmelab.com/react-admin/).
Use `yarn install` after cloning this repo. It needs at least Synapse v1.13.0 for all functions to work as expected!
Use `yarn start` to launch the webserver. ## Step-By-Step install:
You have two options:
1. Download the source code from github and run using nodejs
2. Run the Docker container
Steps for 1):
- make sure you have installed the following: git, yarn, nodejs
- download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git`
- change into downloaded directory: `cd synapse-admin`
- download dependencies: `yarn install`
- start web server: `yarn start`
Steps for 2):
- run the Docker container: `docker run -p 8080:80 awesometechnologies/synapse-admin`
- browse to http://localhost:8080
## Screenshots
![Screenshots](./screenshots.jpg)
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "synapse-admin", "name": "synapse-admin",
"version": "0.1.0", "version": "0.2.1",
"description": "Admin GUI for the Matrix.org server Synapse", "description": "Admin GUI for the Matrix.org server Synapse",
"author": "Awesome Technologies Innovationslabor GmbH", "author": "Awesome Technologies Innovationslabor GmbH",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -29,8 +29,8 @@
"react-scripts": "^3.4.1" "react-scripts": "^3.4.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",
"build": "react-scripts build", "build": "REACT_APP_VERSION=$(git describe --tags) react-scripts build",
"fix:other": "yarn prettier --write", "fix:other": "yarn prettier --write",
"fix:code": "yarn test:lint --fix", "fix:code": "yarn test:lint --fix",
"fix": "yarn fix:code && yarn fix:other", "fix": "yarn fix:code && yarn fix:other",
+8 -1
View File
@@ -38,5 +38,12 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<footer
style="position: relative; z-index: 2; height: 2em; margin-top: -2em; line-height: 2em; background-color: #eee; border: 0.5px solid #ddd">
<a id="copyright" href="https://github.com/Awesome-Technologies/synapse-admin"
style="margin-left: 1em; color: #888; font-family: Roboto, Helvetica, Arial, sans-serif; font-weight: 100; font-size: 0.8em; text-decoration: none;">
Synapse-Admin <b>(%REACT_APP_VERSION%)</b> by Awesome Technologies Innovationslabor GmbH
</a>
</footer>
</body> </body>
</html> </html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

+35 -2
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { import {
fetchUtils, fetchUtils,
FormDataConsumer, FormDataConsumer,
@@ -29,7 +29,7 @@ const useStyles = makeStyles(theme => ({
main: { main: {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
minHeight: "100vh", minHeight: "calc(100vh - 1em)",
alignItems: "center", alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
background: "url(./images/floating-cogs.svg)", background: "url(./images/floating-cogs.svg)",
@@ -40,6 +40,7 @@ const useStyles = makeStyles(theme => ({
card: { card: {
minWidth: "30em", minWidth: "30em",
marginTop: "6em", marginTop: "6em",
marginBottom: "6em",
}, },
avatar: { avatar: {
margin: "1em", margin: "1em",
@@ -64,6 +65,12 @@ const useStyles = makeStyles(theme => ({
actions: { actions: {
padding: "0 1em 1em 1em", padding: "0 1em 1em 1em",
}, },
serverVersion: {
color: "#9e9e9e",
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
marginBottom: "1em",
marginLeft: "0.5em",
},
})); }));
const LoginPage = ({ theme }) => { const LoginPage = ({ theme }) => {
@@ -137,6 +144,7 @@ const LoginPage = ({ theme }) => {
const UserData = ({ formData }) => { const UserData = ({ formData }) => {
const form = useForm(); const form = useForm();
const [serverVersion, setServerVersion] = useState("");
const handleUsernameChange = _ => { const handleUsernameChange = _ => {
if (formData.base_url) return; if (formData.base_url) return;
@@ -157,6 +165,30 @@ const LoginPage = ({ theme }) => {
} }
}; };
useEffect(
_ => {
if (
!formData.base_url ||
!formData.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+$/)
)
return;
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
fetchUtils
.fetchJson(versionUrl, { method: "GET" })
.then(({ json }) => {
setServerVersion(
`${translate("synapseadmin.auth.server_version")} ${
json["server_version"]
}`
);
})
.catch(_ => {
setServerVersion("");
});
},
[formData.base_url]
);
return ( return (
<div> <div>
<div className={classes.input}> <div className={classes.input}>
@@ -189,6 +221,7 @@ const LoginPage = ({ theme }) => {
fullWidth fullWidth
/> />
</div> </div>
<div className={classes.serverVersion}>{serverVersion}</div>
</div> </div>
); );
}; };
+48
View File
@@ -7,8 +7,10 @@ import {
Toolbar, Toolbar,
required, required,
useCreate, useCreate,
useMutation,
useNotify, useNotify,
useTranslate, useTranslate,
useUnselectAll,
} from "react-admin"; } from "react-admin";
import MessageIcon from "@material-ui/icons/Message"; import MessageIcon from "@material-ui/icons/Message";
import IconCancel from "@material-ui/icons/Cancel"; import IconCancel from "@material-ui/icons/Cancel";
@@ -98,3 +100,49 @@ export const ServerNoticeButton = ({ record }) => {
</Fragment> </Fragment>
); );
}; };
export const ServerNoticeBulkButton = ({ selectedIds }) => {
const [open, setOpen] = useState(false);
const notify = useNotify();
const unselectAll = useUnselectAll();
const [createMany, { loading }] = useMutation();
const handleDialogOpen = () => setOpen(true);
const handleDialogClose = () => setOpen(false);
const handleSend = values => {
createMany(
{
type: "createMany",
resource: "servernotices",
payload: { ids: selectedIds, data: values },
},
{
onSuccess: ({ data }) => {
notify("resources.servernotices.action.send_success");
unselectAll("users");
handleDialogClose();
},
onFailure: error =>
notify("resources.servernotices.action.send_failure", "error"),
}
);
};
return (
<Fragment>
<Button
label="resources.servernotices.send"
onClick={handleDialogOpen}
disabled={loading}
>
<MessageIcon />
</Button>
<ServerNoticeDialog
open={open}
onClose={handleDialogClose}
onSend={handleSend}
/>
</Fragment>
);
};
+54 -3
View File
@@ -1,17 +1,68 @@
import React from "react"; import React from "react";
import { Datagrid, List, TextField, Pagination } from "react-admin"; import {
Datagrid,
List,
TextField,
Pagination,
BooleanField,
useTranslate,
} from "react-admin";
import get from "lodash/get";
import { Tooltip, Typography } from "@material-ui/core";
import HttpsIcon from "@material-ui/icons/Https";
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
const RoomPagination = props => ( const RoomPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
); );
const EncryptionField = ({ source, record = {}, emptyText }) => {
const translate = useTranslate();
const value = get(record, source);
let ariaLabel = value === false ? "ra.boolean.false" : "ra.boolean.true";
if (value === false || value === true) {
return (
<Typography component="span" variant="body2">
<Tooltip title={translate(ariaLabel, { _: ariaLabel })}>
{value === true ? (
<HttpsIcon data-testid="true" htmlColor="limegreen" />
) : (
<NoEncryptionIcon data-testid="false" color="error" />
)}
</Tooltip>
</Typography>
);
}
return (
<Typography component="span" variant="body2">
{emptyText}
</Typography>
);
};
export const RoomList = props => ( export const RoomList = props => (
<List {...props} pagination={<RoomPagination />}> <List
{...props}
pagination={<RoomPagination />}
sort={{ field: "name", order: "ASC" }}
>
<Datagrid> <Datagrid>
<TextField source="room_id" /> <EncryptionField
source="is_encrypted"
sortBy="encryption"
label={<HttpsIcon />}
/>
<TextField source="room_id" sortable={false} />
<TextField source="name" /> <TextField source="name" />
<TextField source="canonical_alias" /> <TextField source="canonical_alias" />
<TextField source="joined_members" /> <TextField source="joined_members" />
<TextField source="joined_local_members" />
<TextField source="state_events" />
<TextField source="version" />
<BooleanField source="federatable" />
<BooleanField source="public" />
</Datagrid> </Datagrid>
</List> </List>
); );
+141 -95
View File
@@ -1,5 +1,7 @@
import React, { Fragment } from "react"; import React, { Fragment } from "react";
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 SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent"; import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import { import {
ArrayInput, ArrayInput,
@@ -17,7 +19,6 @@ import {
FormTab, FormTab,
BooleanField, BooleanField,
BooleanInput, BooleanInput,
ImageField,
PasswordInput, PasswordInput,
TextField, TextField,
TextInput, TextInput,
@@ -30,7 +31,20 @@ import {
useTranslate, useTranslate,
Pagination, Pagination,
} from "react-admin"; } from "react-admin";
import { ServerNoticeButton } from "./ServerNotices"; import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles({
small: {
height: "40px",
width: "40px",
},
large: {
height: "120px",
width: "120px",
float: "right",
},
});
const UserPagination = props => ( const UserPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@@ -51,6 +65,7 @@ const UserBulkActionButtons = props => {
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<Fragment> <Fragment>
<ServerNoticeBulkButton {...props} />
<BulkDeleteButton <BulkDeleteButton
{...props} {...props}
label="resources.users.action.erase" label="resources.users.action.erase"
@@ -60,40 +75,36 @@ const UserBulkActionButtons = props => {
); );
}; };
export const UserList = props => ( const AvatarField = ({ source, className, record = {} }) => (
<List <Avatar src={record[source]} className={className} />
{...props}
filters={<UserFilter />}
filterDefaultValues={{ guests: true, deactivated: false }}
bulkActionButtons={<UserBulkActionButtons />}
pagination={<UserPagination />}
>
<Datagrid rowClick="edit">
<ReferenceField
source="Avatar"
reference="users"
link={false}
sortable={false}
>
<ImageField source="avatar_url" title="displayname" />
</ReferenceField>
<TextField source="id" />
{/* Hack since the users endpoint does not give displaynames in the list*/}
<ReferenceField
source="name"
reference="users"
link={false}
sortable={false}
>
<TextField source="displayname" />
</ReferenceField>
<BooleanField source="is_guest" sortable={false} />
<BooleanField source="admin" sortable={false} />
<BooleanField source="deactivated" sortable={false} />
</Datagrid>
</List>
); );
export const UserList = props => {
const classes = useStyles();
return (
<List
{...props}
filters={<UserFilter />}
filterDefaultValues={{ guests: true, deactivated: false }}
bulkActionButtons={<UserBulkActionButtons />}
pagination={<UserPagination />}
>
<Datagrid rowClick="edit">
<AvatarField
source="avatar_src"
sortable={false}
className={classes.small}
/>
<TextField source="id" sortable={false} />
<TextField source="displayname" sortable={false} />
<BooleanField source="is_guest" sortable={false} />
<BooleanField source="admin" sortable={false} />
<BooleanField source="deactivated" sortable={false} />
</Datagrid>
</List>
);
};
// https://matrix.org/docs/spec/appendices#user-identifiers // https://matrix.org/docs/spec/appendices#user-identifiers
const validateUser = regex( const validateUser = regex(
/^@[a-z0-9._=\-/]+:.*/, /^@[a-z0-9._=\-/]+:.*/,
@@ -141,69 +152,104 @@ const UserTitle = ({ record }) => {
const translate = useTranslate(); const translate = useTranslate();
return ( return (
<span> <span>
{translate("resources.users.name")}{" "} {translate("resources.users.name", {
smart_count: 1,
})}{" "}
{record ? `"${record.displayname}"` : ""} {record ? `"${record.displayname}"` : ""}
</span> </span>
); );
}; };
export const UserEdit = props => ( export const UserEdit = props => {
<Edit {...props} title={<UserTitle />}> const classes = useStyles();
<TabbedForm toolbar={<UserEditToolbar />}> return (
<FormTab label="resources.users.name" icon={<PersonPinIcon />}> <Edit {...props} title={<UserTitle />}>
<TextInput source="id" disabled /> <TabbedForm toolbar={<UserEditToolbar />}>
<TextInput source="displayname" /> <FormTab label="resources.users.name" icon={<PersonPinIcon />}>
<PasswordInput source="password" autoComplete="new-password" /> <AvatarField
<BooleanInput source="admin" /> source="avatar_src"
<BooleanInput sortable={false}
source="deactivated" className={classes.large}
helperText="resources.users.helper.deactivate" />
/> <TextInput source="id" disabled />
<ArrayInput source="threepids"> <TextInput source="displayname" />
<SimpleFormIterator> <PasswordInput source="password" autoComplete="new-password" />
<SelectInput <BooleanInput source="admin" />
source="medium" <BooleanInput
choices={[ source="deactivated"
{ id: "email", name: "resources.users.email" }, helperText="resources.users.helper.deactivate"
{ id: "msisdn", name: "resources.users.msisdn" }, />
]} <DateField
/> source="creation_ts_ms"
<TextInput source="address" /> showTime
</SimpleFormIterator> options={{
</ArrayInput> year: "numeric",
</FormTab> month: "2-digit",
<FormTab day: "2-digit",
label="resources.connections.name" hour: "2-digit",
icon={<SettingsInputComponentIcon />} minute: "2-digit",
> second: "2-digit",
<ReferenceField reference="connections" source="id" addLabel={false}> }}
<ArrayField />
source="devices[].sessions[0].connections" <TextField source="consent_version" />
label="resources.connections.name" </FormTab>
<FormTab
label="resources.users.threepid"
icon={<ContactMailIcon />}
path="threepid"
>
<ArrayInput source="threepids">
<SimpleFormIterator>
<SelectInput
source="medium"
choices={[
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
/>
<TextInput source="address" />
</SimpleFormIterator>
</ArrayInput>
</FormTab>
<FormTab
label="resources.connections.name"
icon={<SettingsInputComponentIcon />}
path="connections"
>
<ReferenceField
reference="connections"
source="id"
addLabel={false}
link={false}
> >
<Datagrid style={{ width: "100%" }}> <ArrayField
<TextField source="ip" sortable={false} /> source="devices[].sessions[0].connections"
<DateField label="resources.connections.name"
source="last_seen" >
showTime <Datagrid style={{ width: "100%" }}>
options={{ <TextField source="ip" sortable={false} />
year: "numeric", <DateField
month: "2-digit", source="last_seen"
day: "2-digit", showTime
hour: "2-digit", options={{
minute: "2-digit", year: "numeric",
second: "2-digit", month: "2-digit",
}} day: "2-digit",
sortable={false} hour: "2-digit",
/> minute: "2-digit",
<TextField second: "2-digit",
source="user_agent" }}
sortable={false} sortable={false}
style={{ width: "100%" }} />
/> <TextField
</Datagrid> source="user_agent"
</ArrayField> sortable={false}
</ReferenceField> style={{ width: "100%" }}
</FormTab> />
</TabbedForm> </Datagrid>
</Edit> </ArrayField>
); </ReferenceField>
</FormTab>
</TabbedForm>
</Edit>
);
};
+11
View File
@@ -6,6 +6,7 @@ export default {
auth: { auth: {
base_url: "Heimserver URL", base_url: "Heimserver URL",
welcome: "Willkommen bei Synapse-admin", welcome: "Willkommen bei Synapse-admin",
server_version: "Synapse Version",
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'", username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen", protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
url_error: "Keine gültige Matrix Server URL", url_error: "Keine gültige Matrix Server URL",
@@ -21,6 +22,7 @@ export default {
name: "Benutzer", name: "Benutzer",
email: "E-Mail", email: "E-Mail",
msisdn: "Telefon", msisdn: "Telefon",
threepid: "E-Mail / Telefon",
fields: { fields: {
avatar: "Avatar", avatar: "Avatar",
id: "Benutzer-ID", id: "Benutzer-ID",
@@ -34,9 +36,12 @@ export default {
displayname: "Anzeigename", displayname: "Anzeigename",
password: "Passwort", password: "Passwort",
avatar_url: "Avatar URL", avatar_url: "Avatar URL",
avatar_src: "Avatar",
medium: "Medium", medium: "Medium",
threepids: "3PIDs", threepids: "3PIDs",
address: "Adresse", address: "Adresse",
creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen",
}, },
helper: { helper: {
deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.", deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.",
@@ -53,6 +58,12 @@ export default {
name: "Name", name: "Name",
canonical_alias: "Alias", canonical_alias: "Alias",
joined_members: "Mitglieder", joined_members: "Mitglieder",
joined_local_members: "lokale Mitglieder",
state_events: "Ereignisse",
version: "Version",
is_encrypted: "Verschlüsselt",
federatable: "Fö­de­riert",
public: "Öffentlich",
}, },
}, },
connections: { connections: {
+10
View File
@@ -21,6 +21,7 @@ export default {
name: "User |||| Users", name: "User |||| Users",
email: "Email", email: "Email",
msisdn: "Phone", msisdn: "Phone",
threepid: "Email / Phone",
fields: { fields: {
avatar: "Avatar", avatar: "Avatar",
id: "User-ID", id: "User-ID",
@@ -34,9 +35,12 @@ export default {
displayname: "Displayname", displayname: "Displayname",
password: "Password", password: "Password",
avatar_url: "Avatar URL", avatar_url: "Avatar URL",
avatar_src: "Avatar",
medium: "Medium", medium: "Medium",
threepids: "3PIDs", threepids: "3PIDs",
address: "Address", address: "Address",
creation_ts_ms: "Creation timestamp",
consent_version: "Consent version",
}, },
helper: { helper: {
deactivate: "Deactivated users cannot be reactivated", deactivate: "Deactivated users cannot be reactivated",
@@ -53,6 +57,12 @@ export default {
name: "Name", name: "Name",
canonical_alias: "Alias", canonical_alias: "Alias",
joined_members: "Members", joined_members: "Members",
joined_local_members: "local members",
state_events: "State events",
version: "Version",
is_encrypted: "Encrypted",
federatable: "Federatable",
public: "Public",
}, },
}, },
connections: { connections: {
+52 -5
View File
@@ -14,22 +14,32 @@ const jsonClient = (url, options = {}) => {
return fetchUtils.fetchJson(url, options); return fetchUtils.fetchJson(url, options);
}; };
const mxcUrlToHttp = mxcUrl => {
const homeserver = localStorage.getItem("base_url");
const re = /^mxc:\/\/([^/]+)\/(\w+)/;
var ret = re.exec(mxcUrl);
console.log("mxcClient " + ret);
if (ret == null) return null;
const serverName = ret[1];
const mediaId = ret[2];
return `${homeserver}/_matrix/media/r0/thumbnail/${serverName}/${mediaId}?width=24&height=24&method=scale`;
};
const resourceMap = { const resourceMap = {
users: { users: {
path: "/_synapse/admin/v2/users", path: "/_synapse/admin/v2/users",
map: u => ({ map: u => ({
...u, ...u,
id: u.name, id: u.name,
avatar_src: mxcUrlToHttp(u.avatar_url),
is_guest: !!u.is_guest, is_guest: !!u.is_guest,
admin: !!u.admin, admin: !!u.admin,
deactivated: !!u.deactivated, deactivated: !!u.deactivated,
// need timestamp in milliseconds
creation_ts_ms: u.creation_ts * 1000,
}), }),
data: "users", data: "users",
total: (json, from, perPage) => { total: json => json.total,
return json.next_token
? parseInt(json.next_token, 10) + perPage
: from + json.users.length;
},
create: data => ({ create: data => ({
endpoint: `/_synapse/admin/v2/users/${data.id}`, endpoint: `/_synapse/admin/v2/users/${data.id}`,
body: data, body: data,
@@ -48,6 +58,9 @@ const resourceMap = {
id: r.room_id, id: r.room_id,
alias: r.canonical_alias, alias: r.canonical_alias,
members: r.joined_members, members: r.joined_members,
is_encrypted: !!r.encryption,
federatable: !!r.federatable,
public: !!r.public,
}), }),
data: "rooms", data: "rooms",
total: json => { total: json => {
@@ -86,11 +99,20 @@ function filterNullValues(key, value) {
return value; return value;
} }
function getSearchOrder(order) {
if (order === "DESC") {
return "b";
} else {
return "f";
}
}
const dataProvider = { const dataProvider = {
getList: (resource, params) => { getList: (resource, params) => {
console.log("getList " + resource); console.log("getList " + resource);
const { user_id, guests, deactivated } = params.filter; const { user_id, guests, deactivated } = params.filter;
const { page, perPage } = params.pagination; const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const from = (page - 1) * perPage; const from = (page - 1) * perPage;
const query = { const query = {
from: from, from: from,
@@ -98,6 +120,8 @@ const dataProvider = {
user_id: user_id, user_id: user_id,
guests: guests, guests: guests,
deactivated: deactivated, deactivated: deactivated,
order_by: field,
dir: getSearchOrder(order),
}; };
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();
@@ -221,6 +245,29 @@ const dataProvider = {
})); }));
}, },
createMany: (resource, params) => {
console.log("createMany " + resource);
const homeserver = localStorage.getItem("base_url");
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
const res = resourceMap[resource];
if (!("create" in res)) return Promise.reject();
return Promise.all(
params.ids.map(id => {
params.data.id = id;
const cre = res["create"](params.data);
const endpoint_url = homeserver + cre.endpoint;
return jsonClient(endpoint_url, {
method: cre.method,
body: JSON.stringify(cre.body, filterNullValues),
});
})
).then(responses => ({
data: responses.map(({ json }) => json),
}));
},
delete: (resource, params) => { delete: (resource, params) => {
console.log("delete " + resource); console.log("delete " + resource);
const homeserver = localStorage.getItem("base_url"); const homeserver = localStorage.getItem("base_url");
+3 -3
View File
@@ -11444,9 +11444,9 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1" websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1: websocket-extensions@>=0.1.1:
version "0.1.3" version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
version "1.0.5" version "1.0.5"