diff --git a/README.md b/README.md
index a2e0190..722d54b 100644
--- a/README.md
+++ b/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).
diff --git a/package.json b/package.json
index 6e599ee..cb9132c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/App.js b/src/App.js
index 6ad0b22..3afdff8 100644
--- a/src/App.js
+++ b/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 = () => (
(
icon={UserIcon}
/>
+
+
+
+
+
);
diff --git a/src/components/EventReports.js b/src/components/EventReports.js
new file mode 100644
index 0000000..c73647f
--- /dev/null
+++ b/src/components/EventReports.js
@@ -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 => (
+
+);
+
+export const ReportShow = props => {
+ const translate = useTranslate();
+ return (
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ path="detail"
+ >
+ {" "}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ReportList = ({ ...props }) => {
+ return (
+
}
+ sort={{ field: "received_ts", order: "DESC" }}
+ bulkActionButtons={false}
+ >
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/rooms.js b/src/components/rooms.js
index 10dc987..e1a44e6 100644
--- a/src/components/rooms.js
+++ b/src/components/rooms.js
@@ -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 (
+
+
+
+ );
+};
+
export const RoomShow = props => {
const translate = useTranslate();
return (
- }>
+ } title={}>
}>
diff --git a/src/components/statistics.js b/src/components/statistics.js
new file mode 100644
index 0000000..f14a2fa
--- /dev/null
+++ b/src/components/statistics.js
@@ -0,0 +1,42 @@
+import React from "react";
+import {
+ Datagrid,
+ Filter,
+ List,
+ NumberField,
+ TextField,
+ SearchInput,
+ Pagination,
+} from "react-admin";
+
+const UserMediaStatsPagination = props => (
+
+);
+
+const UserMediaStatsFilter = props => (
+
+
+
+);
+
+export const UserMediaStatsList = props => {
+ return (
+
}
+ pagination={}
+ sort={{ field: "media_length", order: "DESC" }}
+ bulkActionButtons={false}
+ >
+ "/users/" + id + "/media"}>
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/users.js b/src/components/users.js
index 5f8caf8..e66eb44 100644
--- a/src/components/users.js
+++ b/src/components/users.js
@@ -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 => {
/>
+
}
@@ -330,6 +335,7 @@ export const UserEdit = props => {
+
}
@@ -361,6 +367,7 @@ export const UserEdit = props => {
+
}
@@ -400,6 +407,111 @@ export const UserEdit = props => {
+
+ }
+ path="media"
+ >
+ }
+ perPage={50}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ path="rooms"
+ >
+
+ "/rooms/" + id + "/show"}
+ >
+
+
+
+
+
+
+
+
+ }
+ path="pushers"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/i18n/de.js b/src/i18n/de.js
index 7e73b63..269a4f8 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -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: {
@@ -173,6 +175,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: {
@@ -198,6 +224,33 @@ 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",
+ },
+ },
+ 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",
@@ -214,9 +267,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",
diff --git a/src/i18n/en.js b/src/i18n/en.js
index 8ec1903..c322fd1 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -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,8 +140,8 @@ export default {
name: "Name",
canonical_alias: "Alias",
joined_members: "Members",
- joined_local_members: "local members",
- joined_local_devices: "local devices",
+ joined_local_members: "Local members",
+ joined_local_devices: "Local devices",
state_events: "State events",
version: "Version",
is_encrypted: "Encrypted",
@@ -171,6 +173,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: {
@@ -196,6 +222,33 @@ 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",
+ },
+ },
+ 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",
@@ -212,5 +265,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",
+ },
+ },
},
};
diff --git a/src/synapse/authProvider.js b/src/synapse/authProvider.js
index 30ce600..972d599 100644
--- a/src/synapse/authProvider.js
+++ b/src/synapse/authProvider.js
@@ -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();
},
diff --git a/src/synapse/dataProvider.js b/src/synapse/dataProvider.js
index 6d5f78d..d928462 100644
--- a/src/synapse/dataProvider.js
+++ b/src/synapse/dataProvider.js
@@ -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,52 @@ 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}`,
+ }),
},
servernotices: {
map: n => ({ id: n.event_id }),
@@ -118,6 +174,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 +268,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 +279,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),
diff --git a/yarn.lock b/yarn.lock
index cad6848..0a394ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2643,10 +2643,10 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
- version "4.11.9"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
- integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
+ version "4.12.0"
+ resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
+ integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.0.0, bn.js@^5.1.1:
version "5.1.3"
@@ -2717,7 +2717,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-brorand@^1.0.1:
+brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@@ -4207,17 +4207,17 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.591:
integrity sha512-nLO2Wd2yU42eSoNJVQKNf89CcEGqeFZd++QsnN2XIgje1s/19AgctfjLIbPORlvcCO8sYjLwX4iUgDdusOY8Sg==
elliptic@^6.5.3:
- version "6.5.3"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
- integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
+ version "6.5.4"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
+ integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
dependencies:
- bn.js "^4.4.0"
- brorand "^1.0.1"
+ bn.js "^4.11.9"
+ brorand "^1.1.0"
hash.js "^1.0.0"
- hmac-drbg "^1.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.0"
+ hmac-drbg "^1.0.1"
+ inherits "^2.0.4"
+ minimalistic-assert "^1.0.1"
+ minimalistic-crypto-utils "^1.0.1"
emoji-regex@^7.0.1, emoji-regex@^7.0.2:
version "7.0.3"
@@ -5528,7 +5528,7 @@ history@^4.9.0:
tiny-warning "^1.0.0"
value-equal "^1.0.1"
-hmac-drbg@^1.0.0:
+hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@@ -5855,9 +5855,9 @@ inherits@2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.5:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
- integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
+ integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
inquirer@7.0.4:
version "7.0.4"
@@ -7468,7 +7468,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=