22 Commits

Author SHA1 Message Date
Manuel Stahl
ab649fbf70 Merge branch 'master' into amp.chat
Change-Id: I6141964157bcb7218e2e6368a3ca8d20eb4855e9
2020-05-05 13:44:06 +02:00
Manuel Stahl
c41b8ab846 Add ServerNoticeButton to UserEditToolbar
For this, the feature "Server Notices" must be activated on the server.

Change-Id: If3873dc5548822a06a7be0c55e48835c9fb8f78f
2020-05-05 13:34:16 +02:00
Dirk Klimpel
7f16f784f9 Allow port in homeserver URL (#40)
Allow to use homeserver URL with port (e.g. ':443').
2020-05-04 21:28:04 +02:00
Michael Albert
1002b6464a Build with relative paths
Change-Id: I50e47cf4d5a68fa84972c1f205eb066d5df3f9db
2020-05-04 18:28:06 +02:00
Michael Albert
50b770a312 Extract homeserver URL from fully qualified user id
Also lookup the .well-known entry and use it if available.

Change-Id: I609046f01860fd5e3ba8cb801006e6098a4ad840
2020-05-02 16:24:53 +02:00
Manuel Stahl
2d0ce50444 Save base_url from login input
Change-Id: I58447145dfc2df4ab3544b6a165721f900e29b24
2020-05-02 16:22:53 +02:00
Manuel Stahl
1fb89c9e58 Add missing german translations
Change-Id: I297a730f73a4a4aa47a4ce679bd13ef0af69cc38
2020-05-02 16:22:53 +02:00
Michael Albert
8a4c0fe0fe Use input components for LoginPage
Change-Id: Icaaa579eaeaaafe183fb027e4d3bf206f8f5516a
2020-05-02 16:22:53 +02:00
Michael Albert
dd022eab04 Validate URL on input instead of automatic rewrite of http to https
Change-Id: I3f3a9c5fb408af1f03ef876456133b331dc4cea3
2020-05-02 16:22:40 +02:00
Timo Paulssen
880223e5de Offer invitations in room creation
Turns the "Create Room" form into a tabbed form with
tabs that mimic the room display. In the "Members" tab
an AutocompleteArrayInput allows selecting multiple
users by their displayname.

The displayname is also what is displayed ìn the
invitations list.

Creating the room immediately sends out the invitations
as well.

Change-Id: I3915144114ffe4c629848363c9cb7917634d04d8
2020-04-28 19:36:15 +02:00
Manuel Stahl
76fdc80e3e Merge branch 'master' into amp.chat
Change-Id: I08a7a34e041993c29bb12fff52d07534374cda4e
2020-04-28 16:35:40 +02:00
Manuel Stahl
375649756f Add page to show room details
Change-Id: Iec4f402c4322d775cc14c567069a3295ad383b44
2020-04-28 16:30:47 +02:00
Manuel Stahl
662735a91f Adapt for changes in v2/users API
Change-Id: I927b81882fa20e5b3de3d9fc216e2136f7036bba
2020-04-28 16:30:47 +02:00
Manuel Stahl
437fd70d6d Make creating users a special case in dataProvider
Since users are created with PUT instead of POST, this is actually a
special case.

Change-Id: Ibe430fcac4d81de9723abd650804ffa93f87bf6d
2020-04-23 16:57:24 +02:00
Manuel Stahl
1e6e526e3c Show displayname as title for user
Change-Id: I0ba8e2265e5b8e1fe392f56052e96e0243cd3eb6
2020-04-23 16:50:58 +02:00
Manuel Stahl
0823976edd Cleanup room creation
Change-Id: Ieb5189513d21606f8d0bea5692112350a68f2e14
2020-04-23 16:31:26 +02:00
Manuel Stahl
d812cff5fc Rename homeserver_url to endpoint_url in dataProvider
Change-Id: I86441cd90e9b9b6b04ba2d28fee12a1cfa0684a7
2020-04-23 14:29:13 +02:00
Manuel Stahl
a39033e25b yarn: Upgrade packages
Change-Id: Id01070e1e4ca2c2dd42c8ffcd278a57cb31b7b51
2020-04-23 14:29:00 +02:00
Michael Albert
d3cd2e9e33 Fix localStorage entry of homeserver url
Change-Id: I206e3b4428df1f51d4281ad4db26bd64bdffb85d
2020-04-21 17:42:43 +02:00
Timo Paulssen
24abcd4e4a Normalize alias a little, display initial sigil
turns all whitespace into underscores, shows leading
sigil if the alias is non-empty, so the user doesn't get
confused about whether they have to input a # or not.

Change-Id: Ic81e69cc3f0074d63a67b976c9bda32f8de025de
2020-04-20 19:31:33 +02:00
Timo Paulssen
c1c32e3268 Offer "alias" field in room create form
Tries its best to not allow aliases that are too
long (full alias including leading #, : in the
middle, and homeserver domain name must not exceed
255 bytes.

Change-Id: I1e784a94cb599eca7e30736d666b20e37aad5444
2020-04-20 19:31:33 +02:00
Timo Paulssen
ca15435625 Offer room creation form
A choice of public or private is offered, which maps to matrix'
visibility parameter. A name can also be provided.

Change-Id: I34d99acbc4624a9ed54ca6f6609573d5fc1049da
2020-04-20 19:31:33 +02:00
11 changed files with 631 additions and 237 deletions

View File

@@ -4,6 +4,7 @@
"description": "Admin GUI for the Matrix.org server Synapse",
"author": "Awesome Technologies Innovationslabor GmbH",
"license": "Apache-2.0",
"homepage": ".",
"repository": {
"type": "git",
"url": "https://github.com/Awesome-Technologies/synapse-admin"

View File

@@ -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 } from "./components/rooms";
import { RoomList, RoomCreate, RoomShow } from "./components/rooms";
import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group";
import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList";
@@ -40,8 +40,15 @@ const App = () => (
edit={UserEdit}
icon={UserIcon}
/>
<Resource name="rooms" list={RoomList} icon={RoomIcon} />
<Resource
name="rooms"
list={RoomList}
create={RoomCreate}
show={RoomShow}
icon={RoomIcon}
/>
<Resource name="connections" />
<Resource name="servernotices" />
</Admin>
);

View File

@@ -1,13 +1,17 @@
import React, { useState } from "react";
import {
fetchUtils,
FormDataConsumer,
Notification,
useLogin,
useNotify,
useLocale,
useSetLocale,
useTranslate,
PasswordInput,
TextInput,
} from "react-admin";
import { Field, Form } from "react-final-form";
import { Form, useForm } from "react-final-form";
import {
Avatar,
Button,
@@ -34,7 +38,7 @@ const useStyles = makeStyles(theme => ({
backgroundSize: "cover",
},
card: {
minWidth: 300,
minWidth: "30em",
marginTop: "6em",
},
avatar: {
@@ -70,7 +74,7 @@ const LoginPage = ({ theme }) => {
var locale = useLocale();
const setLocale = useSetLocale();
const translate = useTranslate();
const homeserver = localStorage.getItem("base_url");
const base_url = localStorage.getItem("base_url");
const renderInput = ({
meta: { touched, error } = {},
@@ -88,15 +92,23 @@ const LoginPage = ({ theme }) => {
const validate = values => {
const errors = {};
if (!values.homeserver) {
errors.homeserver = translate("ra.validation.required");
}
if (!values.username) {
errors.username = translate("ra.validation.required");
}
if (!values.password) {
errors.password = translate("ra.validation.required");
}
if (!values.base_url) {
errors.base_url = translate("ra.validation.required");
} else {
if (!values.base_url.match(/^(http|https):\/\//)) {
errors.base_url = translate("synapseadmin.auth.protocol_error");
} else if (
!values.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/)
) {
errors.base_url = translate("synapseadmin.auth.url_error");
}
}
return errors;
};
@@ -115,9 +127,75 @@ const LoginPage = ({ theme }) => {
});
};
const extractHomeServer = username => {
const usernameRegex = /@[a-zA-Z0-9._=\-/]+:([a-zA-Z0-9\-.]+\.[a-zA-Z]+)/;
if (!username) return null;
const res = username.match(usernameRegex);
if (res) return res[1];
return null;
};
const UserData = ({ formData }) => {
const form = useForm();
const handleUsernameChange = _ => {
if (formData.base_url) return;
// check if username is a full qualified userId then set base_url accordially
const home_server = extractHomeServer(formData.username);
const wellKnownUrl = `https://${home_server}/.well-known/matrix/client`;
if (home_server) {
// fetch .well-known entry to get base_url
fetchUtils
.fetchJson(wellKnownUrl, { method: "GET" })
.then(({ json }) => {
form.change("base_url", json["m.homeserver"].base_url);
})
.catch(_ => {
// if there is no .well-known entry, try the home server name
form.change("base_url", `https://${home_server}`);
});
}
};
return (
<div>
<div className={classes.input}>
<TextInput
autoFocus
name="username"
component={renderInput}
label={translate("ra.auth.username")}
disabled={loading}
onBlur={handleUsernameChange}
fullWidth
/>
</div>
<div className={classes.input}>
<PasswordInput
name="password"
component={renderInput}
label={translate("ra.auth.password")}
type="password"
disabled={loading}
fullWidth
/>
</div>
<div className={classes.input}>
<TextInput
name="base_url"
component={renderInput}
label={translate("synapseadmin.auth.base_url")}
disabled={loading}
fullWidth
/>
</div>
</div>
);
};
return (
<Form
initialValues={{ homeserver: homeserver }}
initialValues={{ base_url: base_url }}
onSubmit={handleSubmit}
validate={validate}
render={({ handleSubmit }) => (
@@ -146,32 +224,9 @@ const LoginPage = ({ theme }) => {
<MenuItem value="en">English</MenuItem>
</Select>
</div>
<div className={classes.input}>
<Field
autoFocus
name="homeserver"
component={renderInput}
label={translate("synapseadmin.auth.homeserver")}
disabled={loading}
/>
</div>
<div className={classes.input}>
<Field
name="username"
component={renderInput}
label={translate("ra.auth.username")}
disabled={loading}
/>
</div>
<div className={classes.input}>
<Field
name="password"
component={renderInput}
label={translate("ra.auth.password")}
type="password"
disabled={loading}
/>
</div>
<FormDataConsumer>
{formDataProps => <UserData {...formDataProps} />}
</FormDataConsumer>
</div>
<CardActions className={classes.actions}>
<Button

View File

@@ -0,0 +1,100 @@
import React, { Fragment, useState } from "react";
import {
Button,
SaveButton,
SimpleForm,
TextInput,
Toolbar,
required,
useCreate,
useNotify,
useTranslate,
} from "react-admin";
import MessageIcon from "@material-ui/icons/Message";
import IconCancel from "@material-ui/icons/Cancel";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
const translate = useTranslate();
const ServerNoticeToolbar = props => (
<Toolbar {...props}>
<SaveButton label="resources.servernotices.action.send" />
<Button label="ra.action.cancel" onClick={onClose}>
<IconCancel />
</Button>
</Toolbar>
);
return (
<Dialog open={open} onClose={onClose} loading={loading}>
<DialogTitle>
{translate("resources.servernotices.action.send")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{translate("resources.servernotices.helper.send")}
</DialogContentText>
<SimpleForm
toolbar={<ServerNoticeToolbar />}
submitOnEnter={false}
redirect={false}
save={onSend}
>
<TextInput
source="body"
label="resources.servernotices.fields.body"
fullWidth
multiline
rows="4"
resettable
validate={required()}
/>
</SimpleForm>
</DialogContent>
</Dialog>
);
};
export const ServerNoticeButton = ({ record }) => {
const [open, setOpen] = useState(false);
const notify = useNotify();
const [create, { loading }] = useCreate("servernotices");
const handleDialogOpen = () => setOpen(true);
const handleDialogClose = () => setOpen(false);
const handleSend = values => {
create(
{ payload: { data: { id: record.id, ...values } } },
{
onSuccess: () => {
notify("resources.servernotices.action.send_success");
handleDialogClose();
},
onFailure: () =>
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>
);
};

View File

@@ -1,5 +1,24 @@
import React from "react";
import { Datagrid, List, TextField, Pagination } from "react-admin";
import {
AutocompleteArrayInput,
BooleanInput,
Create,
Datagrid,
FormTab,
List,
Pagination,
ReferenceArrayField,
ReferenceArrayInput,
Show,
Tab,
TabbedForm,
TabbedShowLayout,
TextField,
TextInput,
useTranslate,
} from "react-admin";
import ViewListIcon from "@material-ui/icons/ViewList";
import UserIcon from "@material-ui/icons/Group";
const RoomPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@@ -7,7 +26,7 @@ const RoomPagination = props => (
export const RoomList = props => (
<List {...props} pagination={<RoomPagination />}>
<Datagrid>
<Datagrid rowClick="show">
<TextField source="room_id" />
<TextField source="name" />
<TextField source="canonical_alias" />
@@ -15,3 +34,134 @@ export const RoomList = props => (
</Datagrid>
</List>
);
const validateDisplayName = fieldval =>
fieldval === undefined
? "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 */
var te;
// Support for TextEncoder is quite widespread, but the polyfill is
// pretty large; We will only underestimate the size with the regular
// length attribute of String, so we never prevent the user from using
// an alias that is short enough for the server, but too long for our
// heuristic.
try {
te = new TextEncoder();
} catch (err) {
if (err instanceof ReferenceError) {
te = undefined;
}
}
const aliasLength = te === undefined ? alias.length : te.encode(alias).length;
return "#".length + aliasLength + ":".length + homeserver.length;
}
const validateAlias = fieldval => {
if (fieldval === undefined) {
return undefined;
}
const homeserver = localStorage.getItem("home_server");
if (approximateAliasLength(fieldval, homeserver) > 255) {
return "synapseadmin.rooms.alias_too_long";
}
};
const removeLeadingWhitespace = fieldVal =>
fieldVal === undefined ? undefined : fieldVal.trimStart();
const replaceAllWhitespace = fieldVal =>
fieldVal === undefined ? undefined : fieldVal.replace(/\s/, "_");
const removeLeadingSigil = fieldVal =>
fieldVal === undefined
? undefined
: fieldVal.startsWith("#")
? fieldVal.substr(1)
: fieldVal;
const validateHasAliasIfPublic = formdata => {
let errors = {};
if (formdata.public) {
if (
formdata.canonical_alias === undefined ||
formdata.canonical_alias.trim().length === 0
) {
errors.canonical_alias = "synapseadmin.rooms.alias_required_if_public";
}
}
return errors;
};
export const RoomCreate = props => (
<Create {...props}>
<TabbedForm validate={validateHasAliasIfPublic}>
<FormTab label="synapseadmin.rooms.details" icon={<ViewListIcon />}>
<TextInput
source="name"
parse={removeLeadingWhitespace}
validate={validateDisplayName}
/>
<TextInput
source="canonical_alias"
parse={fv => replaceAllWhitespace(removeLeadingSigil(fv))}
validate={validateAlias}
placeholder="#"
/>
<BooleanInput source="public" label="synapseadmin.rooms.make_public" />
</FormTab>
<FormTab
label="resources.rooms.fields.invite_members"
icon={<UserIcon />}
>
<ReferenceArrayInput
reference="users"
source="invitees"
filterToQuery={searchText => ({ user_id: searchText })}
>
<AutocompleteArrayInput
optionText="displayname"
suggestionText="displayname"
/>
</ReferenceArrayInput>
</FormTab>
</TabbedForm>
</Create>
);
const RoomTitle = ({ record }) => {
const translate = useTranslate();
return (
<span>
{translate("resources.rooms.name", 1)} {record ? `"${record.name}"` : ""}
</span>
);
};
export const RoomShow = props => (
<Show {...props} title={<RoomTitle />}>
<TabbedShowLayout>
<Tab label="synapseadmin.rooms.details" icon={<ViewListIcon />}>
<TextField source="id" disabled />
<TextField source="name" />
<TextField source="canonical_alias" />
<TextField source="join_rules" />
<TextField source="guest_access" />
</Tab>
<Tab label="resources.rooms.fields.joined_members" icon={<UserIcon />}>
<ReferenceArrayField reference="users" source="members">
<Datagrid>
<TextField source="id" />
<TextField source="displayname" />
</Datagrid>
</ReferenceArrayField>
</Tab>
</TabbedShowLayout>
</Show>
);

View File

@@ -32,6 +32,7 @@ import {
Pagination,
} from "react-admin";
import SaveQrButton from "./SaveQrButton";
import { ServerNoticeButton } from "./ServerNotices";
const UserPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@@ -39,7 +40,7 @@ const UserPagination = props => (
const UserFilter = props => (
<Filter {...props}>
<SearchInput source="q" alwaysOn />
<SearchInput source="user_id" alwaysOn />
<BooleanInput source="guests" alwaysOn />
<BooleanInput
label="resources.users.fields.show_deactivated"
@@ -71,24 +72,9 @@ export const UserList = props => (
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>
<ImageField source="avatar_url" title="displayname" />
<TextField source="id" sortable={false} />
<TextField source="displayname" />
<BooleanField source="is_guest" sortable={false} />
<BooleanField source="admin" sortable={false} />
<BooleanField source="deactivated" sortable={false} />
@@ -190,6 +176,7 @@ const UserEditToolbar = props => {
label="resources.users.action.erase"
title={translate("resources.users.helper.erase")}
/>
<ServerNoticeButton />
</Toolbar>
);
};
@@ -217,8 +204,17 @@ export const UserCreate = props => (
</Create>
);
const UserTitle = ({ record }) => {
const translate = useTranslate();
return (
<span>
{translate("resources.users.name")}{" "}
{record ? `"${record.displayname}"` : ""}
</span>
);
};
export const UserEdit = props => (
<Edit {...props}>
<Edit {...props} title={<UserTitle />}>
<TabbedForm toolbar={<UserEditToolbar />}>
<FormTab label="resources.users.name" icon={<PersonPinIcon />}>
<TextInput source="id" disabled />

View File

@@ -4,8 +4,11 @@ export default {
...germanMessages,
synapseadmin: {
auth: {
homeserver: "Heimserver",
base_url: "Heimserver URL",
welcome: "Willkommen bei Synapse-admin",
username_error: "Bitte vollständigen Nutzernamen angeben: '@user:domain'",
protocol_error: "Die URL muss mit 'http://' oder 'https://' beginnen",
url_error: "Keine gültige Matrix Server URL",
},
action: {
save_and_show: "Speichern und QR Code erzeugen",
@@ -16,6 +19,16 @@ export default {
invalid_user_id:
"Muss eine vollständige Matrix Benutzer-ID sein, z.B. @benutzer_id:homeserver",
},
rooms: {
details: "Raumdetails",
room_name: "Raumname",
make_public: "Öffentlicher Raum",
room_name_required: "Muss angegeben werden",
alias_required_if_public: "Muss für öffentliche Räume angegeben werden.",
alias: "Alias",
alias_too_long:
"Darf zusammen mit der Domain des Homeservers 255 bytes nicht überschreiten",
},
},
resources: {
users: {
@@ -55,6 +68,8 @@ export default {
name: "Name",
canonical_alias: "Alias",
joined_members: "Mitglieder",
invite_members: "Mitglieder einladen",
invitees: "Einladungen",
},
},
connections: {
@@ -65,6 +80,41 @@ export default {
user_agent: "User Agent",
},
},
servernotices: {
name: "Serverbenachrichtigungen",
send: "Servernachricht versenden",
fields: {
body: "Nachricht",
},
action: {
send: "Sende Nachricht",
send_success: "Nachricht erfolgreich versendet.",
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
},
helper: {
send:
'Sendet eine Serverbenachrichtigung an die ausgewählten Nutzer. Hierfür muss das Feature "Server Notices" auf dem Server aktiviert sein.',
},
},
},
ra: {
...germanMessages.ra,
auth: {
...germanMessages.ra.auth,
auth_check_error: "Anmeldung fehlgeschlagen",
},
input: {
...germanMessages.ra.input,
password: {
...germanMessages.ra.input.password,
toggle_hidden: "Anzeigen",
toggle_visible: "Verstecken",
},
},
notification: {
...germanMessages.ra.notifiaction,
logged_out: "Abgemeldet",
},
},
ra: {
...germanMessages.ra,

View File

@@ -4,8 +4,11 @@ export default {
...englishMessages,
synapseadmin: {
auth: {
homeserver: "Homeserver",
base_url: "Homeserver URL",
welcome: "Welcome to Synapse-admin",
username_error: "Please enter fully qualified user ID: '@user:domain'",
protocol_error: "URL has to start with 'http://' or 'https://'",
url_error: "Not a valid Matrix server URL",
},
action: {
save_and_show: "Create QR code",
@@ -16,6 +19,16 @@ export default {
invalid_user_id:
"Must be a fully qualified Matrix user-id, e.g. @user_id:homeserver",
},
rooms: {
details: "Room Details",
room_name: "Room Name",
make_public: "Make room public",
room_name_required: "Must be provided",
alias_required_if_public: "Must be provided for a public room",
alias: "Alias",
alias_too_long:
"Must not exceed 255 bytes including the domain of the homeserver.",
},
},
resources: {
users: {
@@ -55,6 +68,9 @@ export default {
name: "Name",
canonical_alias: "Alias",
joined_members: "Members",
invite_members: "Invite Members",
invitees: "Invitations",
},
},
connections: {
@@ -65,5 +81,21 @@ export default {
user_agent: "User agent",
},
},
servernotices: {
name: "Server Notices",
send: "Send server notices",
fields: {
body: "Message",
},
action: {
send: "Send note",
send_success: "Server notice successfully sent.",
send_failure: "An error has occurred.",
},
helper: {
send:
'Sends a server notice to the selected users. The feature "Server Notices" has to be activated at the server.',
},
},
},
};

View File

@@ -1,23 +1,8 @@
import { fetchUtils } from "react-admin";
const ensureHttpsForUrl = url => {
if (/^https:\/\//i.test(url)) {
return url;
}
const domain = url.replace(/http.?:\/\//g, "");
return "https://" + domain;
};
const stripTrailingSlash = str => {
if (!str) {
return;
}
return str.endsWith("/") ? str.slice(0, -1) : str;
};
const authProvider = {
// called when the user attempts to log in
login: ({ homeserver, username, password }) => {
login: ({ base_url, username, password }) => {
console.log("login ");
const options = {
method: "POST",
@@ -28,17 +13,16 @@ const authProvider = {
}),
};
const url = window.decodeURIComponent(homeserver);
const trimmed_url = url.trim().replace(/\s/g, "");
const login_api_url =
ensureHttpsForUrl(trimmed_url) + "/_matrix/client/r0/login";
// use the base_url from login instead of the well_known entry from the
// server, since the admin might want to access the admin API via some
// private address
localStorage.setItem("base_url", base_url);
const decoded_base_url = window.decodeURIComponent(base_url);
const login_api_url = decoded_base_url + "/_matrix/client/r0/login";
return fetchUtils.fetchJson(login_api_url, options).then(({ json }) => {
const normalized_base_url = stripTrailingSlash(
json.well_known["m.homeserver"].base_url
);
localStorage.setItem("base_url", normalized_base_url);
localStorage.setItem("home_server_url", json.home_server);
localStorage.setItem("home_server", json.home_server);
localStorage.setItem("user_id", json.user_id);
localStorage.setItem("access_token", json.access_token);
localStorage.setItem("device_id", json.device_id);

View File

@@ -23,13 +23,15 @@ const resourceMap = {
is_guest: !!u.is_guest,
admin: !!u.admin,
deactivated: !!u.deactivated,
displayname: u.display_name || u.displayname,
}),
data: "users",
total: (json, from, perPage) => {
return json.next_token
? parseInt(json.next_token, 10) + perPage
: from + json.users.length;
},
total: json => json.total,
create: data => ({
endpoint: `/_synapse/admin/v2/users/${data.id}`,
body: data,
method: "PUT",
}),
delete: id => ({
endpoint: `/_synapse/admin/v1/deactivate/${id}`,
body: { erase: true },
@@ -41,13 +43,22 @@ const resourceMap = {
map: r => ({
...r,
id: r.room_id,
alias: r.canonical_alias,
members: r.joined_members,
}),
data: "rooms",
total: json => {
return json.total_rooms;
},
total: json => json.total_rooms,
create: data => ({
endpoint: "/_matrix/client/r0/createRoom",
body: {
name: data.name,
room_alias_name: data.canonical_alias,
visibility: data.public ? "public" : "private",
invite:
Array.isArray(data.invitees) && data.invitees.length > 0
? data.invitees
: undefined,
},
method: "POST",
})
},
connections: {
path: "/_synapse/admin/v1/whois",
@@ -57,6 +68,20 @@ const resourceMap = {
}),
data: "connections",
},
servernotices: {
map: n => ({ id: n.event_id }),
create: data => ({
endpoint: "/_synapse/admin/v1/send_server_notice",
body: {
user_id: data.id,
content: {
msgtype: "m.text",
body: data.body,
},
},
method: "POST",
}),
},
};
function filterNullValues(key, value) {
@@ -85,27 +110,8 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
const url = `${homeserver_url}?${stringify(query)}`;
// searching for users is not implemented in admin api
if (params.filter.q) {
console.log("searching");
const search_query = { limit: perPage, search_term: params.filter.q };
const search_url =
homeserver + `/_matrix/client/r0/user_directory/search`;
return jsonClient(search_url, {
method: "POST",
body: JSON.stringify(search_query),
}).then(({ json }) => ({
data: json["results"].map(u => ({
...u,
id: u.user_id,
name: u.user_id,
})),
total: json.limited ? perPage * 2 : json.results.length,
}));
}
const endpoint_url = homeserver + res.path;
const url = `${endpoint_url}?${stringify(query)}`;
return jsonClient(url).then(({ json }) => ({
data: json[res.data].map(res.map),
@@ -120,8 +126,8 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
return jsonClient(`${homeserver_url}/${params.id}`).then(({ json }) => ({
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`).then(({ json }) => ({
data: res.map(json),
}));
},
@@ -133,9 +139,9 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
const endpoint_url = homeserver + res.path;
return Promise.all(
params.ids.map(id => jsonClient(`${homeserver_url}/${id}`))
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`))
).then(responses => ({
data: responses.map(({ json }) => res.map(json)),
}));
@@ -160,8 +166,8 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
const url = `${homeserver_url}?${stringify(query)}`;
const endpoint_url = homeserver + res.path;
const url = `${endpoint_url}?${stringify(query)}`;
return jsonClient(url).then(({ headers, json }) => ({
data: json,
@@ -176,8 +182,8 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
return jsonClient(`${homeserver_url}/${params.data.id}`, {
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.data.id}`, {
method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
}).then(({ json }) => ({
@@ -192,9 +198,9 @@ const dataProvider = {
const res = resourceMap[resource];
const homeserver_url = homeserver + res.path;
const endpoint_url = homeserver + res.path;
return Promise.all(
params.ids.map(id => jsonClient(`${homeserver_url}/${id}`), {
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`), {
method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
})
@@ -209,11 +215,13 @@ const dataProvider = {
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
const res = resourceMap[resource];
if (!("create" in res)) return Promise.reject();
const homeserver_url = homeserver + res.path;
return jsonClient(`${homeserver_url}/${params.data.id}`, {
method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
const create = res["create"](params.data);
const endpoint_url = homeserver + create.endpoint;
return jsonClient(endpoint_url, {
method: create.method,
body: JSON.stringify(create.body, filterNullValues),
}).then(({ json }) => ({
data: res.map(json),
}));
@@ -228,16 +236,16 @@ const dataProvider = {
if ("delete" in res) {
const del = res["delete"](params.id);
const homeserver_url = homeserver + del.endpoint;
return jsonClient(homeserver_url, {
const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, {
method: del.method,
body: JSON.stringify(del.body),
}).then(({ json }) => ({
data: json,
}));
} else {
const homeserver_url = homeserver + res.path;
return jsonClient(`${homeserver_url}/${params.id}`, {
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`, {
method: "DELETE",
body: JSON.stringify(params.data, filterNullValues),
}).then(({ json }) => ({
@@ -257,8 +265,8 @@ const dataProvider = {
return Promise.all(
params.ids.map(id => {
const del = res["delete"](id);
const homeserver_url = homeserver + del.endpoint;
return jsonClient(homeserver_url, {
const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, {
method: del.method,
body: JSON.stringify(del.body),
});
@@ -267,10 +275,10 @@ const dataProvider = {
data: responses.map(({ json }) => json),
}));
} else {
const homeserver_url = homeserver + res.path;
const endpoint_url = homeserver + res.path;
return Promise.all(
params.ids.map(id =>
jsonClient(`${homeserver_url}/${id}`, {
jsonClient(`${endpoint_url}/${id}`, {
method: "DELETE",
body: JSON.stringify(params.data, filterNullValues),
})

211
yarn.lock
View File

@@ -1142,10 +1142,10 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
"@jest/types@^25.2.6":
version "25.2.6"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.2.6.tgz#c12f44af9bed444438091e4b59e7ed05f8659cb6"
integrity sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ==
"@jest/types@^25.4.0":
version "25.4.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.4.0.tgz#5afeb8f7e1cba153a28e5ac3c9fe3eede7206d59"
integrity sha512-XBeaWNzw2PPnGW5aXvZt3+VO60M+34RY3XDsCK5tW7kyj3RK0XClRutCfjqcBuaR2aBQTbluEDME9b5MB9UAPw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
@@ -1153,19 +1153,20 @@
chalk "^3.0.0"
"@material-ui/core@^4.3.3":
version "4.9.9"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.9.9.tgz#902dc37eeb415dd3288feb2c92e0dc27d9caed48"
integrity sha512-Gp0UdJLxPEnkn7O0QpJ2/LOeIuT8nX9e6CjQFuLnOy10rUGjRsOZ2T170Y057xdUmw1VNE+0bvkkO6dOghxt4g==
version "4.9.11"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.9.11.tgz#02f45f4dbea6db191fdc0d993323d64cfae613fd"
integrity sha512-S2Ha9GpTxzl29XMeMc8dQX2pj97yApNzuhe/23If53fMdg5Fmd3SgbE1bMbyXeKhxwtXZjOFxd0vU+W/sez8Ew==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.9.6"
"@material-ui/system" "^4.9.6"
"@material-ui/types" "^5.0.0"
"@material-ui/react-transition-group" "^4.2.0"
"@material-ui/styles" "^4.9.10"
"@material-ui/system" "^4.9.10"
"@material-ui/types" "^5.0.1"
"@material-ui/utils" "^4.9.6"
"@types/react-transition-group" "^4.2.0"
clsx "^1.0.2"
clsx "^1.0.4"
hoist-non-react-statics "^3.3.2"
popper.js "^1.14.1"
popper.js "^1.16.1-lts"
prop-types "^15.7.2"
react-is "^16.8.0"
react-transition-group "^4.3.0"
@@ -1177,14 +1178,24 @@
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles@^4.3.3", "@material-ui/styles@^4.9.6":
version "4.9.6"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.9.6.tgz#924a30bf7c9b91af9c8f19c12c8573b8a4ecd085"
integrity sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==
"@material-ui/react-transition-group@^4.2.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@material-ui/react-transition-group/-/react-transition-group-4.3.0.tgz#92529142addb5cc179dbf42d246c7e3fe4d6104b"
integrity sha512-CwQ0aXrlUynUTY6sh3UvKuvye1o92en20VGAs6TORnSxUYeRmkX8YeTUN3lAkGiBX1z222FxLFO36WWh6q73rQ==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
"@material-ui/styles@^4.3.3", "@material-ui/styles@^4.9.10":
version "4.9.10"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.9.10.tgz#182ccdd0bc8525a459486499bbaebcd92b0db3ab"
integrity sha512-EXIXlqVyFDnjXF6tj72y6ZxiSy+mHtrsCo3Srkm3XUeu3Z01aftDBy7ZSr3TQ02gXHTvDSBvegp3Le6p/tl7eA==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/hash" "^0.8.0"
"@material-ui/types" "^5.0.0"
"@material-ui/types" "^5.0.1"
"@material-ui/utils" "^4.9.6"
clsx "^1.0.2"
csstype "^2.5.2"
@@ -1199,19 +1210,19 @@
jss-plugin-vendor-prefixer "^10.0.3"
prop-types "^15.7.2"
"@material-ui/system@^4.9.6":
version "4.9.6"
resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.6.tgz#fd060540224da4d1740da8ca6e7af288e217717e"
integrity sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==
"@material-ui/system@^4.9.10":
version "4.9.10"
resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.10.tgz#5de6ec7bea0f222b10b45e5bd5bb8b9a7b938926"
integrity sha512-E+t0baX2TBZk6ALm8twG6objpsxLdMM4MDm1++LMt2m7CetCAEc3aIAfDaprk4+tm5hFT1Cah5dRWk8EeIFQYw==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/utils" "^4.9.6"
prop-types "^15.7.2"
"@material-ui/types@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.0.0.tgz#26d6259dc6b39f4c2e1e9aceff7a11e031941741"
integrity sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==
"@material-ui/types@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.0.1.tgz#c4954063cdc196eb327ee62c041368b1aebb6d61"
integrity sha512-wURPSY7/3+MAtng3i26g+WKwwNE3HEeqa/trDBR5+zWKmcjO+u9t7Npu/J1r+3dmIa/OeziN9D/18IrBKvKffw==
"@material-ui/utils@^4.9.6":
version "4.9.6"
@@ -1418,9 +1429,9 @@
wait-for-expect "^1.2.0"
"@testing-library/dom@^7.1.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.2.0.tgz#948894c2ef52017d299c35da02e085498363cd1e"
integrity sha512-K1Sao38VxsTrjTkFkzeW8m/oCtgCI5lANCE7u9ZaF+TTL3uKuiZ+vazeurxjvRHAsE6PvXjOIl6JFuZfgcWJSQ==
version "7.2.1"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.2.1.tgz#bb3b31d669bbe0c4939dadd95d69caa3c1d0b372"
integrity sha512-xIGoHlQ2ZiEL1dJIFKNmLDypzYF+4OJTTASRctl/aoIDaS5y/pRVHRigoqvPUV11mdJoR71IIgi/6UviMgyz4g==
dependencies:
"@babel/runtime" "^7.9.2"
"@types/testing-library__dom" "^7.0.0"
@@ -1429,9 +1440,9 @@
pretty-format "^25.1.0"
"@testing-library/jest-dom@^5.1.1":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.3.0.tgz#2ae813b8b0eb69e8808f75d3af8efa3f0dc4d7ec"
integrity sha512-Cdhpc3BHL888X55qBNyra9eM0UG63LCm/FqCWTa1Ou/0MpsUbQTM9vW1NU6/jBQFoSLgkFfDG5XVpm2V0dOm/A==
version "5.5.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.5.0.tgz#4707023e8f572021e8a84f65721303ff60828d88"
integrity sha512-7sWHrpxG4Yd8TmryI7Rtbx8Ff4mbs3ASye3oshQIuHvsCR+QHgr7rTR/PfeXvOmwUwR36wSTTAvrLKsPmr6VEQ==
dependencies:
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.0.2"
@@ -1444,9 +1455,9 @@
redent "^3.0.0"
"@testing-library/react@^10.0.2":
version "10.0.2"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.2.tgz#8eca7aa52d810cf7150048a2829fdc487162006d"
integrity sha512-YT6Mw0oJz7R6vlEkmo1FlUD+K15FeXApOB5Ffm9zooFVnrwkt00w18dUJFMOh1yRp9wTdVRonbor7o4PIpFCmA==
version "10.0.3"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.3.tgz#7781adc75cce172f8cda28faa77be29c6270ab43"
integrity sha512-EVmd3ghDFBEjOSLnISUdi7OcLdP6tsqjmTprpMaBz5TreoM8jnxGKIPkLD579CE0LYGqK01iffQiy6wwW/RUig==
dependencies:
"@babel/runtime" "^7.9.2"
"@testing-library/dom" "^7.1.0"
@@ -1461,9 +1472,9 @@
"@testing-library/dom" "^5.6.1"
"@testing-library/user-event@^10.0.1":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-10.0.1.tgz#9a02dbbc813135f25778b17f92a63933a58e8af5"
integrity sha512-M63ftowo1QpAGMnWyz7df0ygqnu4XyF68Sty7mivMAz2HLcY1uLoN3qcen6WMobdY0MoZUi4+BLsziSDAP62Vg==
version "10.0.2"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-10.0.2.tgz#c44197998a9b7a8d60ff66bb9d41635d9648f064"
integrity sha512-fVeP4U37BIYdp9nBRKEITFSLPqgCSS7Og6LHvxoQ2JSOTJ1NJI4Dfesv4uNXxvNNcJgBS88V+Tc6h8vbDsa2iA==
"@types/babel__core@^7.1.0":
version "7.1.7"
@@ -1595,9 +1606,9 @@
"@types/react" "*"
"@types/react@*":
version "16.9.32"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.32.tgz#f6368625b224604148d1ddf5920e4fefbd98d383"
integrity sha512-fmejdp0CTH00mOJmxUPPbWCEBWPvRIL4m8r0qD+BSDUqmutPyGQCHifzMpMzdvZwROdEdL78IuZItntFWgPXHQ==
version "16.9.34"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349"
integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
@@ -3023,7 +3034,7 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clsx@^1.0.2:
clsx@^1.0.2, clsx@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702"
integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==
@@ -3266,9 +3277,9 @@ core-js-compat@^3.6.2:
semver "7.0.0"
core-js-pure@^3.0.0:
version "3.6.4"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
core-js@^1.0.0:
version "1.2.7"
@@ -4252,9 +4263,9 @@ escodegen@^1.11.0, escodegen@^1.9.1:
source-map "~0.6.1"
eslint-config-prettier@^6.10.1:
version "6.10.1"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz#129ef9ec575d5ddc0e269667bf09defcd898642a"
integrity sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==
version "6.11.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"
integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==
dependencies:
get-stdin "^6.0.0"
@@ -4333,9 +4344,9 @@ eslint-plugin-jsx-a11y@6.2.3:
jsx-ast-utils "^2.2.1"
eslint-plugin-prettier@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==
version "3.1.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz#ae116a0fc0e598fdae48743a4430903de5b4e6ca"
integrity sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==
dependencies:
prettier-linter-helpers "^1.0.0"
@@ -6223,15 +6234,15 @@ jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.2.6.tgz#a6d70a9ab74507715ea1092ac513d1ab81c1b5e7"
integrity sha512-KuadXImtRghTFga+/adnNrv9s61HudRMR7gVSbP35UKZdn4IK2/0N0PpGZIqtmllK9aUyye54I3nu28OYSnqOg==
jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.4.0:
version "25.4.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.4.0.tgz#260b70f19a46c283adcad7f081cae71eb784a634"
integrity sha512-kklLbJVXW0y8UKOWOdYhI6TH5MG6QAxrWiBMgQaPIuhj3dNFGirKCd+/xfplBXICQ7fI+3QcqHm9p9lWu1N6ug==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.2.6"
jest-get-type "^25.2.6"
pretty-format "^25.2.6"
pretty-format "^25.4.0"
jest-docblock@^24.3.0:
version "24.9.0"
@@ -6356,14 +6367,14 @@ jest-matcher-utils@^24.9.0:
pretty-format "^24.9.0"
jest-matcher-utils@^25.1.0:
version "25.2.7"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.2.7.tgz#53fad3c11fc42e92e374306df543026712c957a3"
integrity sha512-jNYmKQPRyPO3ny0KY1I4f0XW4XnpJ3Nx5ovT4ik0TYDOYzuXJW40axqOyS61l/voWbVT9y9nZ1THL1DlpaBVpA==
version "25.4.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz#dc3e7aec402a1e567ed80b572b9ad285878895e6"
integrity sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==
dependencies:
chalk "^3.0.0"
jest-diff "^25.2.6"
jest-diff "^25.4.0"
jest-get-type "^25.2.6"
pretty-format "^25.2.6"
pretty-format "^25.4.0"
jest-message-util@^24.9.0:
version "24.9.0"
@@ -8220,7 +8231,7 @@ pnp-webpack-plugin@1.6.4:
dependencies:
ts-pnp "^1.1.6"
popper.js@^1.14.1:
popper.js@^1.16.1-lts:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
@@ -8918,9 +8929,9 @@ prettier-linter-helpers@^1.0.0:
fast-diff "^1.1.2"
prettier@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.3.tgz#9a06f0e94a51420e78b6925568b5bec72afe41ea"
integrity sha512-5qpBDBHO9fpE0zruKiTZm8Gxmz7kknO+WlQR/ivV+RMwgDw/WjOgmxLDn66MPrxq/WZPx/EgEZzh87xJO5E6Fw==
version "2.0.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
pretty-bytes@^5.1.0:
version "5.3.0"
@@ -8945,12 +8956,12 @@ pretty-format@^24.8.0, pretty-format@^24.9.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.2.6:
version "25.2.6"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.2.6.tgz#542a1c418d019bbf1cca2e3620443bc1323cb8d7"
integrity sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg==
pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.4.0:
version "25.4.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.4.0.tgz#c58801bb5c4926ff4a677fe43f9b8b99812c7830"
integrity sha512-PI/2dpGjXK5HyXexLPZU/jw5T9Q6S1YVXxxVxco+LIqzUFHXIbKZKdUVt7GcX7QUCr31+3fzhi4gN4/wUYPVxQ==
dependencies:
"@jest/types" "^25.2.6"
"@jest/types" "^25.4.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
@@ -9151,10 +9162,10 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
ra-core@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.4.0.tgz#1d4b49b78cf60eb2b0d2c1584ccfce73611ab9a6"
integrity sha512-hpC0r6s6Uodj3e8T06uvBbk/dcv2H/C2Gih+ouAyuKOR4gglAWIPqcpy6SfN5cZrBxbGkvDCFSKDNNMw5067+g==
ra-core@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-3.4.1.tgz#4390f7f317a1bbdabf03b6cd2d4484d7048450ef"
integrity sha512-XHcYAU36aMhIAmspdQ299SLCclu7qMmPS/IXN1VPVlRJHAIurK5tgRUMStAgDRO05qIkU5Xnb9C9PA63VmlPwA==
dependencies:
"@testing-library/react" "^8.0.7"
classnames "~2.2.5"
@@ -9167,30 +9178,30 @@ ra-core@^3.4.0:
recompose "~0.26.0"
reselect "~3.0.0"
ra-i18n-polyglot@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.4.0.tgz#fcc2067e55e953c58f496d579837241615eca85b"
integrity sha512-53VWZ3C9cza9cqfibzGVBdFAA/zc3c8pLY2yt5RKNiD1taHylZyX0doebC///jOe7OwUaOHv1KnD5WdLaNM93Q==
ra-i18n-polyglot@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-3.4.1.tgz#269618aaae5fd525e36cb52cf1709e5fcd79bb9b"
integrity sha512-i13N/wGi7aOxKeABdJ54wwYJZPUeLLUQ23C1TAbNynFro1pIHl2j4lxRBhRIlYPwNKdsUjuLzt30fdcVIGH+FQ==
dependencies:
node-polyglot "^2.2.2"
ra-core "^3.4.0"
ra-core "^3.4.1"
ra-language-english@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.4.0.tgz#1296dd9bd632f175e71f885c9bdbc06379b49d5a"
integrity sha512-H2ZapYrn4jPyj7/+aA2X4XUaXQ6t2y5gR8DlSsRZlkmlHVWcpUFlyG2gZCYTDAeVKXO5Dedgf09KlYLJ6p7p0w==
ra-language-english@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-3.4.1.tgz#04a117d9991a2da32f313819041cac7aa2a6337c"
integrity sha512-bAoJyIGL3LJ/8hIvQ+gsHYlFKwgpOalQb3ZUdJReU+vAt52nQqN/3BxknxSMRFOxH0iMQk9k3B+EakDtiesH3Q==
dependencies:
ra-core "^3.4.0"
ra-core "^3.4.1"
ra-language-german@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ra-language-german/-/ra-language-german-2.1.2.tgz#d183093d470ab499ece91838cb67222db88f2e4f"
integrity sha512-N+BaBP0z98ujaKVlAMIKTfWHgmTiWD8sPQrU5vA3+b5zY9U0mMB4VjvU8sQQPR7rZE0gsRgS/X4V6ycDtNL6iQ==
ra-ui-materialui@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.4.0.tgz#9f3f573384ee8c8e91637627fb888bd51a4fd0b9"
integrity sha512-nV10aD4Z8ZgmZUcnfNTzAelQCxqdc5cG/KxyYvcqqV9j0oGBrqC9dr00bKLIBHc1oFTVvLwcdPd4p78tO6B+5g==
ra-ui-materialui@^3.4.2:
version "3.4.2"
resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-3.4.2.tgz#6ab59b44cbe351eee2afccfeb118ee3ea67db28d"
integrity sha512-P1WigQJzIGeMy2jpAoMF0PlkDKZuufUEp/J0y9YFPumHvd5Dvzzz4XCytlL+362XkzgtxVWWH+WlX8CoYD3Y2w==
dependencies:
autosuggest-highlight "^3.1.1"
classnames "~2.2.5"
@@ -9257,9 +9268,9 @@ raw-body@2.4.0:
unpipe "1.0.0"
react-admin@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.4.0.tgz#d2806d9bab6f1c73e4f065d6f339389dd8126195"
integrity sha512-tgxCAYPkoZa7LC8ImFU8pQVFf5ja4dbxtx4/bgCOYJ7/QbTYdwFhzwuhfspnzRWDO3Tvkx3VTiuObQuR+JIDug==
version "3.4.2"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-3.4.2.tgz#081a9f930e5822cf31c4cc747d058a9522f945e4"
integrity sha512-C0YeuWgd1fskhOk9oG6uyZidw7IEd5MkQWBKahDOY4PW59dZk8hFSuRzkVkaF8B/c/2oM1kCGuVR5likYPrJEA==
dependencies:
"@material-ui/core" "^4.3.3"
"@material-ui/icons" "^4.2.1"
@@ -9267,10 +9278,10 @@ react-admin@^3.4.0:
connected-react-router "^6.5.2"
final-form "^4.18.5"
final-form-arrays "^3.0.1"
ra-core "^3.4.0"
ra-i18n-polyglot "^3.4.0"
ra-language-english "^3.4.0"
ra-ui-materialui "^3.4.0"
ra-core "^3.4.1"
ra-i18n-polyglot "^3.4.1"
ra-language-english "^3.4.1"
ra-ui-materialui "^3.4.2"
react-final-form "^6.3.3"
react-final-form-arrays "^3.1.1"
react-redux "^7.1.0"
@@ -10587,9 +10598,9 @@ string.prototype.trim@^1.1.2, string.prototype.trim@^1.2.1:
function-bind "^1.1.1"
string.prototype.trimend@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373"
integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"
@@ -10613,9 +10624,9 @@ string.prototype.trimright@^2.1.1:
string.prototype.trimend "^1.0.0"
string.prototype.trimstart@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2"
integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"