commit
774beb07ec
23
README.md
23
README.md
@ -4,7 +4,7 @@
|
||||
|
||||
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
||||
|
||||
It needs at least Synapse v1.18.0 for all functions to work as expected!
|
||||
It needs at least Synapse v1.23.0 for all functions to work as expected!
|
||||
|
||||
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
||||
See also [Synapse version API](https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.rst).
|
||||
@ -35,7 +35,26 @@ Steps for 1):
|
||||
|
||||
Steps for 2):
|
||||
|
||||
- run the Docker container: `docker run -p 8080:80 awesometechnologies/synapse-admin`
|
||||
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the (docker-compose.yml)[docker-compose.yml]: `docker-compose up -d`
|
||||
|
||||
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
synapse-admin:
|
||||
container_name: synapse-admin
|
||||
hostname: synapse-admin
|
||||
build:
|
||||
context: https://github.com/Awesome-Technologies/synapse-admin.git
|
||||
# args:
|
||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||
ports:
|
||||
- "8080:80"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- browse to http://localhost:8080
|
||||
|
||||
## Screenshots
|
||||
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
synapse-admin:
|
||||
container_name: synapse-admin
|
||||
hostname: synapse-admin
|
||||
image: awesometechnologies/synapse-admin:latest
|
||||
# build:
|
||||
# context: .
|
||||
|
||||
# to use the docker-compose as standalone without a local repo clone,
|
||||
# replace the context definition with this:
|
||||
# context: https://github.com/Awesome-Technologies/synapse-admin.git
|
||||
|
||||
# if you're building on an architecture other than amd64, make sure
|
||||
# to define a maximum ram for node. otherwise the build will fail.
|
||||
# args:
|
||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||
ports:
|
||||
- "8080:80"
|
||||
restart: unless-stopped
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "synapse-admin",
|
||||
"version": "0.2.1",
|
||||
"version": "0.7.0",
|
||||
"description": "Admin GUI for the Matrix.org server Synapse",
|
||||
"author": "Awesome Technologies Innovationslabor GmbH",
|
||||
"license": "Apache-2.0",
|
||||
@ -19,14 +19,15 @@
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"prettier": "^2.0.0"
|
||||
"prettier": "^2.0.0",
|
||||
"ra-test": "^3.14.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"papaparse": "^5.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"ra-language-german": "^2.1.2",
|
||||
"react": "^16.13.1",
|
||||
"react-admin": "^3.10.0",
|
||||
"react": "^17.0.0",
|
||||
"react-admin": "^3.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-scripts": "^3.4.4"
|
||||
},
|
||||
|
21
src/App.js
21
src/App.js
@ -5,9 +5,13 @@ import authProvider from "./synapse/authProvider";
|
||||
import dataProvider from "./synapse/dataProvider";
|
||||
import { UserList, UserCreate, UserEdit } from "./components/users";
|
||||
import { RoomList, RoomShow } from "./components/rooms";
|
||||
import { ReportList, ReportShow } from "./components/EventReports";
|
||||
import LoginPage from "./components/LoginPage";
|
||||
import UserIcon from "@material-ui/icons/Group";
|
||||
import { ViewListIcon as RoomIcon } from "@material-ui/icons/ViewList";
|
||||
import EqualizerIcon from "@material-ui/icons/Equalizer";
|
||||
import { UserMediaStatsList } from "./components/statistics";
|
||||
import RoomIcon from "@material-ui/icons/ViewList";
|
||||
import ReportIcon from "@material-ui/icons/Warning";
|
||||
import { ImportFeature } from "./components/ImportFeature";
|
||||
import { Route } from "react-router-dom";
|
||||
import germanMessages from "./i18n/de";
|
||||
@ -25,6 +29,7 @@ const i18nProvider = polyglotI18nProvider(
|
||||
|
||||
const App = () => (
|
||||
<Admin
|
||||
disableTelemetry
|
||||
loginPage={LoginPage}
|
||||
authProvider={authProvider}
|
||||
dataProvider={dataProvider}
|
||||
@ -41,9 +46,23 @@ const App = () => (
|
||||
icon={UserIcon}
|
||||
/>
|
||||
<Resource name="rooms" list={RoomList} show={RoomShow} icon={RoomIcon} />
|
||||
<Resource
|
||||
name="user_media_statistics"
|
||||
list={UserMediaStatsList}
|
||||
icon={EqualizerIcon}
|
||||
/>
|
||||
<Resource
|
||||
name="reports"
|
||||
list={ReportList}
|
||||
show={ReportShow}
|
||||
icon={ReportIcon}
|
||||
/>
|
||||
<Resource name="connections" />
|
||||
<Resource name="devices" />
|
||||
<Resource name="room_members" />
|
||||
<Resource name="users_media" />
|
||||
<Resource name="joined_rooms" />
|
||||
<Resource name="pushers" />
|
||||
<Resource name="servernotices" />
|
||||
</Admin>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { TestContext } from "react-admin";
|
||||
import { TestContext } from "ra-test";
|
||||
import { shallow } from "enzyme";
|
||||
import App from "./App";
|
||||
|
||||
|
135
src/components/EventReports.js
Normal file
135
src/components/EventReports.js
Normal file
@ -0,0 +1,135 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Datagrid,
|
||||
DateField,
|
||||
List,
|
||||
NumberField,
|
||||
Pagination,
|
||||
ReferenceField,
|
||||
Show,
|
||||
Tab,
|
||||
TabbedShowLayout,
|
||||
TextField,
|
||||
useTranslate,
|
||||
} from "react-admin";
|
||||
import PageviewIcon from "@material-ui/icons/Pageview";
|
||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
||||
|
||||
const ReportPagination = props => (
|
||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||
);
|
||||
|
||||
export const ReportShow = props => {
|
||||
const translate = useTranslate();
|
||||
return (
|
||||
<Show {...props}>
|
||||
<TabbedShowLayout>
|
||||
<Tab
|
||||
label={translate("synapseadmin.reports.tabs.basic", {
|
||||
smart_count: 1,
|
||||
})}
|
||||
icon={<ViewListIcon />}
|
||||
>
|
||||
<DateField
|
||||
source="received_ts"
|
||||
showTime
|
||||
options={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}}
|
||||
sortable={true}
|
||||
/>
|
||||
<ReferenceField source="user_id" reference="users">
|
||||
<TextField source="id" />
|
||||
</ReferenceField>
|
||||
<NumberField source="score" />
|
||||
<TextField source="reason" />
|
||||
<TextField source="name" />
|
||||
<TextField
|
||||
source="canonical_alias"
|
||||
label="resources.rooms.fields.canonical_alias"
|
||||
/>
|
||||
<ReferenceField
|
||||
source="room_id"
|
||||
reference="rooms"
|
||||
link="show"
|
||||
label="resources.rooms.fields.room_id"
|
||||
>
|
||||
<TextField source="id" />
|
||||
</ReferenceField>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
label="synapseadmin.reports.tabs.detail"
|
||||
icon={<PageviewIcon />}
|
||||
path="detail"
|
||||
>
|
||||
{" "}
|
||||
<DateField
|
||||
source="event_json.origin_server_ts"
|
||||
showTime
|
||||
options={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}}
|
||||
sortable={true}
|
||||
/>
|
||||
<ReferenceField source="sender" reference="users">
|
||||
<TextField source="id" />
|
||||
</ReferenceField>
|
||||
<TextField source="event_id" />
|
||||
<TextField source="event_json.origin" />
|
||||
<TextField source="event_json.type" />
|
||||
<TextField source="event_json.content.msgtype" />
|
||||
<TextField source="event_json.content.body" />
|
||||
<TextField source="event_json.content.format" />
|
||||
<TextField source="event_json.content.formatted_body" />
|
||||
<TextField source="event_json.content.algorithm" />
|
||||
<TextField
|
||||
source="event_json.content.device_id"
|
||||
label="resources.users.fields.device_id"
|
||||
/>
|
||||
</Tab>
|
||||
</TabbedShowLayout>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReportList = ({ ...props }) => {
|
||||
return (
|
||||
<List
|
||||
{...props}
|
||||
pagination={<ReportPagination />}
|
||||
sort={{ field: "received_ts", order: "DESC" }}
|
||||
bulkActionButtons={false}
|
||||
>
|
||||
<Datagrid rowClick="show">
|
||||
<TextField source="id" sortable={false} />
|
||||
<DateField
|
||||
source="received_ts"
|
||||
showTime
|
||||
options={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}}
|
||||
sortable={true}
|
||||
/>
|
||||
<TextField sortable={false} source="user_id" />
|
||||
<TextField sortable={false} source="name" />
|
||||
<TextField sortable={false} source="score" />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { TestContext } from "react-admin";
|
||||
import { TestContext } from "ra-test";
|
||||
import { shallow } from "enzyme";
|
||||
import LoginPage from "./LoginPage";
|
||||
|
||||
|
145
src/components/media.js
Normal file
145
src/components/media.js
Normal file
@ -0,0 +1,145 @@
|
||||
import React, { Fragment, useState } from "react";
|
||||
import classnames from "classnames";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
BooleanInput,
|
||||
Button,
|
||||
DateTimeInput,
|
||||
NumberInput,
|
||||
SaveButton,
|
||||
SimpleForm,
|
||||
Toolbar,
|
||||
useDelete,
|
||||
useNotify,
|
||||
useTranslate,
|
||||
} from "react-admin";
|
||||
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";
|
||||
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
|
||||
|
||||
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" }
|
||||
);
|
||||
|
||||
const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
||||
const translate = useTranslate();
|
||||
|
||||
const dateParser = v => {
|
||||
const d = new Date(v);
|
||||
if (isNaN(d)) return 0;
|
||||
return d.getTime();
|
||||
};
|
||||
|
||||
const DeleteMediaToolbar = props => {
|
||||
return (
|
||||
<Toolbar {...props}>
|
||||
<SaveButton
|
||||
label="resources.delete_media.action.send"
|
||||
icon={<DeleteSweepIcon />}
|
||||
/>
|
||||
<Button label="ra.action.cancel" onClick={onClose}>
|
||||
<IconCancel />
|
||||
</Button>
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} loading={loading}>
|
||||
<DialogTitle>
|
||||
{translate("resources.delete_media.action.send")}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{translate("resources.delete_media.helper.send")}
|
||||
</DialogContentText>
|
||||
<SimpleForm
|
||||
toolbar={<DeleteMediaToolbar />}
|
||||
submitOnEnter={false}
|
||||
redirect={false}
|
||||
save={onSend}
|
||||
>
|
||||
<DateTimeInput
|
||||
fullWidth
|
||||
source="before_ts"
|
||||
label="resources.delete_media.fields.before_ts"
|
||||
defaultValue={0}
|
||||
parse={dateParser}
|
||||
/>
|
||||
<NumberInput
|
||||
fullWidth
|
||||
source="size_gt"
|
||||
label="resources.delete_media.fields.size_gt"
|
||||
defaultValue={0}
|
||||
min={0}
|
||||
step={1024}
|
||||
/>
|
||||
<BooleanInput
|
||||
fullWidth
|
||||
source="keep_profiles"
|
||||
label="resources.delete_media.fields.keep_profiles"
|
||||
defaultValue={true}
|
||||
/>
|
||||
</SimpleForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const DeleteMediaButton = props => {
|
||||
const classes = useStyles(props);
|
||||
const [open, setOpen] = useState(false);
|
||||
const notify = useNotify();
|
||||
const [deleteOne, { loading }] = useDelete("delete_media");
|
||||
|
||||
const handleDialogOpen = () => setOpen(true);
|
||||
const handleDialogClose = () => setOpen(false);
|
||||
|
||||
const handleSend = values => {
|
||||
deleteOne(
|
||||
{ payload: { ...values } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
notify("resources.delete_media.action.send_success");
|
||||
handleDialogClose();
|
||||
},
|
||||
onFailure: () =>
|
||||
notify("resources.delete_media.action.send_failure", "error"),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
label="resources.delete_media.action.send"
|
||||
onClick={handleDialogOpen}
|
||||
disabled={loading}
|
||||
className={classnames("ra-delete-button", classes.deleteButton)}
|
||||
>
|
||||
<DeleteSweepIcon />
|
||||
</Button>
|
||||
<DeleteMediaDialog
|
||||
open={open}
|
||||
onClose={handleDialogClose}
|
||||
onSend={handleSend}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
@ -4,6 +4,7 @@ import {
|
||||
BooleanField,
|
||||
BulkDeleteWithConfirmButton,
|
||||
Datagrid,
|
||||
DeleteButton,
|
||||
Filter,
|
||||
List,
|
||||
Pagination,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
Tab,
|
||||
TabbedShowLayout,
|
||||
TextField,
|
||||
TopToolbar,
|
||||
useTranslate,
|
||||
} from "react-admin";
|
||||
import get from "lodash/get";
|
||||
@ -70,10 +72,26 @@ const RoomTitle = ({ record }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const RoomShowActions = ({ basePath, data, resource }) => {
|
||||
const translate = useTranslate();
|
||||
return (
|
||||
<TopToolbar>
|
||||
<DeleteButton
|
||||
basePath={basePath}
|
||||
record={data}
|
||||
resource={resource}
|
||||
undoable={false}
|
||||
confirmTitle={translate("synapseadmin.rooms.delete.title")}
|
||||
confirmContent={translate("synapseadmin.rooms.delete.message")}
|
||||
/>
|
||||
</TopToolbar>
|
||||
);
|
||||
};
|
||||
|
||||
export const RoomShow = props => {
|
||||
const translate = useTranslate();
|
||||
return (
|
||||
<Show {...props} title={<RoomTitle />}>
|
||||
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
|
||||
<TabbedShowLayout>
|
||||
<Tab label="synapseadmin.rooms.tabs.basic" icon={<ViewListIcon />}>
|
||||
<TextField source="room_id" />
|
||||
|
81
src/components/statistics.js
Normal file
81
src/components/statistics.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
import { cloneElement } from "react";
|
||||
import {
|
||||
Datagrid,
|
||||
ExportButton,
|
||||
Filter,
|
||||
List,
|
||||
NumberField,
|
||||
Pagination,
|
||||
sanitizeListRestProps,
|
||||
SearchInput,
|
||||
TextField,
|
||||
TopToolbar,
|
||||
useListContext,
|
||||
} from "react-admin";
|
||||
import { DeleteMediaButton } from "./media";
|
||||
|
||||
const ListActions = props => {
|
||||
const { className, exporter, filters, maxResults, ...rest } = props;
|
||||
const {
|
||||
currentSort,
|
||||
resource,
|
||||
displayedFilters,
|
||||
filterValues,
|
||||
showFilter,
|
||||
total,
|
||||
} = useListContext();
|
||||
return (
|
||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||
{filters &&
|
||||
cloneElement(filters, {
|
||||
resource,
|
||||
showFilter,
|
||||
displayedFilters,
|
||||
filterValues,
|
||||
context: "button",
|
||||
})}
|
||||
<DeleteMediaButton />
|
||||
<ExportButton
|
||||
disabled={total === 0}
|
||||
resource={resource}
|
||||
sort={currentSort}
|
||||
filterValues={filterValues}
|
||||
maxResults={maxResults}
|
||||
/>
|
||||
</TopToolbar>
|
||||
);
|
||||
};
|
||||
|
||||
const UserMediaStatsPagination = props => (
|
||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||
);
|
||||
|
||||
const UserMediaStatsFilter = props => (
|
||||
<Filter {...props}>
|
||||
<SearchInput source="search_term" alwaysOn />
|
||||
</Filter>
|
||||
);
|
||||
|
||||
export const UserMediaStatsList = props => {
|
||||
return (
|
||||
<List
|
||||
{...props}
|
||||
actions={<ListActions />}
|
||||
filters={<UserMediaStatsFilter />}
|
||||
pagination={<UserMediaStatsPagination />}
|
||||
sort={{ field: "media_length", order: "DESC" }}
|
||||
bulkActionButtons={false}
|
||||
>
|
||||
<Datagrid rowClick={(id, basePath, record) => "/users/" + id + "/media"}>
|
||||
<TextField source="user_id" label="resources.users.fields.id" />
|
||||
<TextField
|
||||
source="displayname"
|
||||
label="resources.users.fields.displayname"
|
||||
/>
|
||||
<NumberField source="media_count" />
|
||||
<NumberField source="media_length" />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
@ -5,6 +5,9 @@ import ContactMailIcon from "@material-ui/icons/ContactMail";
|
||||
import DevicesIcon from "@material-ui/icons/Devices";
|
||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
||||
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
|
||||
import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
||||
import {
|
||||
ArrayInput,
|
||||
ArrayField,
|
||||
@ -40,6 +43,7 @@ import {
|
||||
ExportButton,
|
||||
TopToolbar,
|
||||
sanitizeListRestProps,
|
||||
NumberField,
|
||||
} from "react-admin";
|
||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||
import { DeviceRemoveButton } from "./devices";
|
||||
@ -312,6 +316,7 @@ export const UserEdit = props => {
|
||||
/>
|
||||
<TextField source="consent_version" />
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label="resources.users.threepid"
|
||||
icon={<ContactMailIcon />}
|
||||
@ -330,6 +335,7 @@ export const UserEdit = props => {
|
||||
</SimpleFormIterator>
|
||||
</ArrayInput>
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label={translate("resources.devices.name", { smart_count: 2 })}
|
||||
icon={<DevicesIcon />}
|
||||
@ -361,6 +367,7 @@ export const UserEdit = props => {
|
||||
</Datagrid>
|
||||
</ReferenceManyField>
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label="resources.connections.name"
|
||||
icon={<SettingsInputComponentIcon />}
|
||||
@ -400,6 +407,111 @@ export const UserEdit = props => {
|
||||
</ArrayField>
|
||||
</ReferenceField>
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label={translate("resources.users_media.name", { smart_count: 2 })}
|
||||
icon={<PermMediaIcon />}
|
||||
path="media"
|
||||
>
|
||||
<ReferenceManyField
|
||||
reference="users_media"
|
||||
target="user_id"
|
||||
addLabel={false}
|
||||
pagination={<UserPagination />}
|
||||
perPage={50}
|
||||
>
|
||||
<Datagrid style={{ width: "100%" }}>
|
||||
<DateField
|
||||
source="created_ts"
|
||||
showTime
|
||||
options={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}}
|
||||
sortable={false}
|
||||
/>
|
||||
<DateField
|
||||
source="last_access_ts"
|
||||
showTime
|
||||
options={{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}}
|
||||
sortable={false}
|
||||
/>
|
||||
<TextField source="media_id" sortable={false} />
|
||||
<NumberField source="media_length" sortable={false} />
|
||||
<TextField source="media_type" sortable={false} />
|
||||
<TextField source="upload_name" sortable={false} />
|
||||
<TextField source="quarantined_by" sortable={false} />
|
||||
<BooleanField source="safe_from_quarantine" sortable={false} />
|
||||
<DeleteButton undoable={false} redirect={false} />
|
||||
</Datagrid>
|
||||
</ReferenceManyField>
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label={translate("resources.rooms.name", { smart_count: 2 })}
|
||||
icon={<ViewListIcon />}
|
||||
path="rooms"
|
||||
>
|
||||
<ReferenceManyField
|
||||
reference="joined_rooms"
|
||||
target="user_id"
|
||||
addLabel={false}
|
||||
>
|
||||
<Datagrid
|
||||
style={{ width: "100%" }}
|
||||
rowClick={(id, basePath, record) => "/rooms/" + id + "/show"}
|
||||
>
|
||||
<TextField
|
||||
source="id"
|
||||
sortable={false}
|
||||
label="resources.rooms.fields.room_id"
|
||||
/>
|
||||
<ReferenceField
|
||||
label="resources.rooms.fields.name"
|
||||
source="id"
|
||||
reference="rooms"
|
||||
sortable={false}
|
||||
link=""
|
||||
>
|
||||
<TextField source="name" sortable={false} />
|
||||
</ReferenceField>
|
||||
</Datagrid>
|
||||
</ReferenceManyField>
|
||||
</FormTab>
|
||||
|
||||
<FormTab
|
||||
label={translate("resources.pushers.name", { smart_count: 2 })}
|
||||
icon={<NotificationsIcon />}
|
||||
path="pushers"
|
||||
>
|
||||
<ReferenceManyField
|
||||
reference="pushers"
|
||||
target="user_id"
|
||||
addLabel={false}
|
||||
>
|
||||
<Datagrid style={{ width: "100%" }}>
|
||||
<TextField source="kind" sortable={false} />
|
||||
<TextField source="app_display_name" sortable={false} />
|
||||
<TextField source="app_id" sortable={false} />
|
||||
<TextField source="data.url" sortable={false} />
|
||||
<TextField source="device_display_name" sortable={false} />
|
||||
<TextField source="lang" sortable={false} />
|
||||
<TextField source="profile_tag" sortable={false} />
|
||||
<TextField source="pushkey" sortable={false} />
|
||||
</Datagrid>
|
||||
</ReferenceManyField>
|
||||
</FormTab>
|
||||
</TabbedForm>
|
||||
</Edit>
|
||||
);
|
||||
|
@ -29,6 +29,7 @@ export default {
|
||||
"Sind Sie sicher dass Sie den Raum löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. Alle Nachrichten und Medien, die der Raum beinhaltet werden vom Server gelöscht!",
|
||||
},
|
||||
},
|
||||
reports: { tabs: { basic: "Allgemein", detail: "Details" } },
|
||||
},
|
||||
import_users: {
|
||||
error: {
|
||||
@ -126,7 +127,8 @@ export default {
|
||||
consent_version: "Zugestimmte Geschäftsbedingungen",
|
||||
},
|
||||
helper: {
|
||||
deactivate: "Deaktivierte Nutzer können nicht wieder aktiviert werden.",
|
||||
deactivate:
|
||||
"Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
|
||||
erase: "DSGVO konformes Löschen der Benutzerdaten",
|
||||
},
|
||||
action: {
|
||||
@ -172,6 +174,30 @@ export default {
|
||||
unencrypted: "Nicht verschlüsselt",
|
||||
},
|
||||
},
|
||||
reports: {
|
||||
name: "Ereignisbericht |||| Ereignisberichte",
|
||||
fields: {
|
||||
id: "ID",
|
||||
received_ts: "Meldezeit",
|
||||
user_id: "Meldender",
|
||||
name: "Raumname",
|
||||
score: "Wert",
|
||||
reason: "Grund",
|
||||
event_id: "Event-ID",
|
||||
event_json: {
|
||||
origin: "Ursprungsserver",
|
||||
origin_server_ts: "Sendezeit",
|
||||
type: "Eventtyp",
|
||||
content: {
|
||||
msgtype: "Inhaltstyp",
|
||||
body: "Nachrichteninhalt",
|
||||
format: "Nachrichtenformat",
|
||||
formatted_body: "Formatierter Nachrichteninhalt",
|
||||
algorithm: "Verschlüsselungsalgorithmus",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
name: "Verbindungen",
|
||||
fields: {
|
||||
@ -197,6 +223,50 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
users_media: {
|
||||
name: "Medien",
|
||||
fields: {
|
||||
media_id: "Medien ID",
|
||||
media_length: "Größe",
|
||||
media_type: "Typ",
|
||||
upload_name: "Dateiname",
|
||||
quarantined_by: "Zur Quarantäne hinzugefügt",
|
||||
safe_from_quarantine: "Geschützt vor Quarantäne",
|
||||
created_ts: "Erstellt",
|
||||
last_access_ts: "Letzter Zugriff",
|
||||
},
|
||||
},
|
||||
delete_media: {
|
||||
name: "Medien",
|
||||
fields: {
|
||||
before_ts: "Letzter Zugriff vor",
|
||||
size_gt: "Größer als (in Bytes)",
|
||||
keep_profiles: "Behalte Profilbilder",
|
||||
},
|
||||
action: {
|
||||
send: "Medien löschen",
|
||||
send_success: "Anfrage erfolgreich versendet.",
|
||||
send_failure: "Beim Versenden ist ein Fehler aufgetreten.",
|
||||
},
|
||||
helper: {
|
||||
send:
|
||||
"Diese API löscht die lokalen Medien von der Festplatte des eigenen Servers. Dies umfasst alle lokalen Miniaturbilder und Kopien von Medien. Diese API wirkt sich nicht auf Medien aus, die sich in externen Medien-Repositories befinden.",
|
||||
},
|
||||
},
|
||||
pushers: {
|
||||
name: "Pusher |||| Pushers",
|
||||
fields: {
|
||||
app: "App",
|
||||
app_display_name: "App-Anzeigename",
|
||||
app_id: "App ID",
|
||||
device_display_name: "Geräte-Anzeigename",
|
||||
kind: "Art",
|
||||
lang: "Sprache",
|
||||
profile_tag: "Profil-Tag",
|
||||
pushkey: "Pushkey",
|
||||
data: { url: "URL" },
|
||||
},
|
||||
},
|
||||
servernotices: {
|
||||
name: "Serverbenachrichtigungen",
|
||||
send: "Servernachricht versenden",
|
||||
@ -213,9 +283,20 @@ export default {
|
||||
'Sendet eine Serverbenachrichtigung an die ausgewählten Nutzer. Hierfür muss das Feature "Server Notices" auf dem Server aktiviert sein.',
|
||||
},
|
||||
},
|
||||
user_media_statistics: {
|
||||
name: "Dateien je Benutzer",
|
||||
fields: {
|
||||
media_count: "Anzahl der Dateien",
|
||||
media_length: "Größe der Dateien",
|
||||
},
|
||||
},
|
||||
},
|
||||
ra: {
|
||||
...germanMessages.ra,
|
||||
action: {
|
||||
...germanMessages.ra.action,
|
||||
unselect: "Abwählen",
|
||||
},
|
||||
auth: {
|
||||
...germanMessages.ra.auth,
|
||||
auth_check_error: "Anmeldung fehlgeschlagen",
|
||||
|
@ -6,6 +6,7 @@ export default {
|
||||
auth: {
|
||||
base_url: "Homeserver URL",
|
||||
welcome: "Welcome to Synapse-admin",
|
||||
server_version: "Synapse version",
|
||||
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",
|
||||
@ -27,6 +28,7 @@ export default {
|
||||
"Are you sure you want to delete the room? This cannot be undone. All messages and shared media in the room will be deleted from the server!",
|
||||
},
|
||||
},
|
||||
reports: { tabs: { basic: "Basic", detail: "Details" } },
|
||||
},
|
||||
import_users: {
|
||||
error: {
|
||||
@ -124,7 +126,7 @@ export default {
|
||||
consent_version: "Consent version",
|
||||
},
|
||||
helper: {
|
||||
deactivate: "Deactivated users cannot be reactivated",
|
||||
deactivate: "You must provide a password to re-activate an account.",
|
||||
erase: "Mark the user as GDPR-erased",
|
||||
},
|
||||
action: {
|
||||
@ -138,7 +140,7 @@ export default {
|
||||
name: "Name",
|
||||
canonical_alias: "Alias",
|
||||
joined_members: "Members",
|
||||
joined_local_members: "local members",
|
||||
joined_local_members: "Local members",
|
||||
state_events: "State events",
|
||||
version: "Version",
|
||||
is_encrypted: "Encrypted",
|
||||
@ -170,6 +172,30 @@ export default {
|
||||
unencrypted: "Unencrypted",
|
||||
},
|
||||
},
|
||||
reports: {
|
||||
name: "Reported event |||| Reported events",
|
||||
fields: {
|
||||
id: "ID",
|
||||
received_ts: "report time",
|
||||
user_id: "announcer",
|
||||
name: "name of the room",
|
||||
score: "score",
|
||||
reason: "reason",
|
||||
event_id: "event ID",
|
||||
event_json: {
|
||||
origin: "origin server",
|
||||
origin_server_ts: "time of send",
|
||||
type: "event typ",
|
||||
content: {
|
||||
msgtype: "content type",
|
||||
body: "content",
|
||||
format: "format",
|
||||
formatted_body: "formatted content",
|
||||
algorithm: "algorithm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
name: "Connections",
|
||||
fields: {
|
||||
@ -195,6 +221,50 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
users_media: {
|
||||
name: "Media",
|
||||
fields: {
|
||||
media_id: "Media ID",
|
||||
media_length: "Lenght",
|
||||
media_type: "Type",
|
||||
upload_name: "File name",
|
||||
quarantined_by: "Quarantined by",
|
||||
safe_from_quarantine: "Safe from quarantine",
|
||||
created_ts: "Created",
|
||||
last_access_ts: "Last access",
|
||||
},
|
||||
},
|
||||
delete_media: {
|
||||
name: "Media",
|
||||
fields: {
|
||||
before_ts: "last access before",
|
||||
size_gt: "Larger then (in bytes)",
|
||||
keep_profiles: "Keep profile images",
|
||||
},
|
||||
action: {
|
||||
send: "Delete media",
|
||||
send_success: "Request successfully sent.",
|
||||
send_failure: "An error has occurred.",
|
||||
},
|
||||
helper: {
|
||||
send:
|
||||
"This API deletes the local media from the disk of your own server. This includes any local thumbnails and copies of media downloaded. This API will not affect media that has been uploaded to external media repositories.",
|
||||
},
|
||||
},
|
||||
pushers: {
|
||||
name: "Pusher |||| Pushers",
|
||||
fields: {
|
||||
app: "App",
|
||||
app_display_name: "App display name",
|
||||
app_id: "App ID",
|
||||
device_display_name: "Device display name",
|
||||
kind: "Kind",
|
||||
lang: "Language",
|
||||
profile_tag: "Profile tag",
|
||||
pushkey: "Pushkey",
|
||||
data: { url: "URL" },
|
||||
},
|
||||
},
|
||||
servernotices: {
|
||||
name: "Server Notices",
|
||||
send: "Send server notices",
|
||||
@ -211,5 +281,12 @@ export default {
|
||||
'Sends a server notice to the selected users. The feature "Server Notices" has to be activated at the server.',
|
||||
},
|
||||
},
|
||||
user_media_statistics: {
|
||||
name: "Users' media",
|
||||
fields: {
|
||||
media_count: "Media count",
|
||||
media_length: "Media length",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ const authProvider = {
|
||||
type: "m.login.password",
|
||||
user: username,
|
||||
password: password,
|
||||
initial_device_display_name: "Synapse Admin",
|
||||
}),
|
||||
};
|
||||
|
||||
@ -30,8 +31,26 @@ const authProvider = {
|
||||
},
|
||||
// called when the user clicks on the logout button
|
||||
logout: () => {
|
||||
console.log("logout ");
|
||||
localStorage.removeItem("access_token");
|
||||
console.log("logout");
|
||||
|
||||
const logout_api_url =
|
||||
localStorage.getItem("base_url") + "/_matrix/client/r0/logout";
|
||||
const access_token = localStorage.getItem("access_token");
|
||||
|
||||
const options = {
|
||||
method: "POST",
|
||||
user: {
|
||||
authenticated: true,
|
||||
token: `Bearer ${access_token}`,
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof access_token === "string") {
|
||||
fetchUtils.fetchJson(logout_api_url, options).then(({ json }) => {
|
||||
localStorage.removeItem("access_token");
|
||||
localStorage.removeItem("device_id");
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
// called when the API returns an error
|
||||
@ -46,7 +65,7 @@ const authProvider = {
|
||||
checkAuth: () => {
|
||||
const access_token = localStorage.getItem("access_token");
|
||||
console.log("checkAuth " + access_token);
|
||||
return typeof access_token == "string"
|
||||
return typeof access_token === "string"
|
||||
? Promise.resolve()
|
||||
: Promise.reject();
|
||||
},
|
||||
|
@ -72,13 +72,24 @@ const resourceMap = {
|
||||
method: "POST",
|
||||
}),
|
||||
},
|
||||
reports: {
|
||||
path: "/_synapse/admin/v1/event_reports",
|
||||
map: er => ({
|
||||
...er,
|
||||
id: er.id,
|
||||
}),
|
||||
data: "event_reports",
|
||||
total: json => json.total,
|
||||
},
|
||||
devices: {
|
||||
map: d => ({
|
||||
...d,
|
||||
id: d.device_id,
|
||||
}),
|
||||
data: "devices",
|
||||
total: json => json.devices.length,
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
reference: id => ({
|
||||
endpoint: `/_synapse/admin/v2/users/${id}/devices`,
|
||||
}),
|
||||
@ -102,7 +113,62 @@ const resourceMap = {
|
||||
endpoint: `/_synapse/admin/v1/rooms/${id}/members`,
|
||||
}),
|
||||
data: "members",
|
||||
total: json => json.members.length,
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
},
|
||||
pushers: {
|
||||
map: p => ({
|
||||
...p,
|
||||
id: p.pushkey,
|
||||
}),
|
||||
reference: id => ({
|
||||
endpoint: `/_synapse/admin/v1/users/${id}/pushers`,
|
||||
}),
|
||||
data: "pushers",
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
},
|
||||
joined_rooms: {
|
||||
map: jr => ({
|
||||
id: jr,
|
||||
}),
|
||||
reference: id => ({
|
||||
endpoint: `/_synapse/admin/v1/users/${id}/joined_rooms`,
|
||||
}),
|
||||
data: "joined_rooms",
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
},
|
||||
users_media: {
|
||||
map: um => ({
|
||||
...um,
|
||||
id: um.media_id,
|
||||
}),
|
||||
reference: id => ({
|
||||
endpoint: `/_synapse/admin/v1/users/${id}/media`,
|
||||
}),
|
||||
data: "media",
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
delete: params => ({
|
||||
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
|
||||
"home_server"
|
||||
)}/${params.id}`,
|
||||
}),
|
||||
},
|
||||
delete_media: {
|
||||
delete: params => ({
|
||||
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
|
||||
"home_server"
|
||||
)}/delete?before_ts=${params.before_ts}&size_gt=${
|
||||
params.size_gt
|
||||
}&keep_profiles=${params.keep_profiles}`,
|
||||
method: "POST",
|
||||
}),
|
||||
},
|
||||
servernotices: {
|
||||
map: n => ({ id: n.event_id }),
|
||||
@ -118,6 +184,17 @@ const resourceMap = {
|
||||
method: "POST",
|
||||
}),
|
||||
},
|
||||
user_media_statistics: {
|
||||
path: "/_synapse/admin/v1/statistics/users/media",
|
||||
map: usms => ({
|
||||
...usms,
|
||||
id: usms.user_id,
|
||||
}),
|
||||
data: "users",
|
||||
total: json => {
|
||||
return json.total;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function filterNullValues(key, value) {
|
||||
@ -201,6 +278,10 @@ const dataProvider = {
|
||||
console.log("getManyReference " + resource);
|
||||
const { page, perPage } = params.pagination;
|
||||
const from = (page - 1) * perPage;
|
||||
const query = {
|
||||
from: from,
|
||||
limit: perPage,
|
||||
};
|
||||
|
||||
const homeserver = localStorage.getItem("base_url");
|
||||
if (!homeserver || !(resource in resourceMap)) return Promise.reject();
|
||||
@ -208,7 +289,7 @@ const dataProvider = {
|
||||
const res = resourceMap[resource];
|
||||
|
||||
const ref = res["reference"](params.id);
|
||||
const endpoint_url = homeserver + ref.endpoint;
|
||||
const endpoint_url = `${homeserver}${ref.endpoint}?${stringify(query)}`;
|
||||
|
||||
return jsonClient(endpoint_url).then(({ headers, json }) => ({
|
||||
data: json[res.data].map(res.map),
|
||||
|
Loading…
Reference in New Issue
Block a user