Add the lists of public rooms on the server (#105)

* Add room directory and the switches to rooms settings

* Fix react admin version
This commit is contained in:
Dirk Klimpel 2021-05-04 13:52:43 +02:00 committed by GitHub
parent 0268cc0e94
commit 2ab4343970
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 381 additions and 40 deletions

View file

@ -12,7 +12,9 @@ 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 FolderSharedIcon from "@material-ui/icons/FolderShared";
import { ImportFeature } from "./components/ImportFeature";
import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom";
import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en";
@ -57,6 +59,11 @@ const App = () => (
show={ReportShow}
icon={ReportIcon}
/>
<Resource
name="room_directory"
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
<Resource name="connections" />
<Resource name="devices" />
<Resource name="room_members" />

View file

@ -0,0 +1,252 @@
import React, { Fragment } from "react";
import Avatar from "@material-ui/core/Avatar";
import { Chip } from "@material-ui/core";
import { connect } from "react-redux";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import { makeStyles } from "@material-ui/core/styles";
import {
BooleanField,
BulkDeleteButton,
Button,
Datagrid,
DeleteButton,
Filter,
List,
NumberField,
Pagination,
TextField,
useCreate,
useMutation,
useNotify,
useTranslate,
useRefresh,
useUnselectAll,
} from "react-admin";
const useStyles = makeStyles({
small: {
height: "40px",
width: "40px",
},
});
const RoomDirectoryPagination = props => (
<Pagination {...props} rowsPerPageOptions={[100, 500, 1000, 2000]} />
);
export const RoomDirectoryDeleteButton = props => {
const translate = useTranslate();
return (
<DeleteButton
{...props}
label="resources.room_directory.action.erase"
redirect={false}
mutationMode="pessimistic"
confirmTitle={translate("resources.room_directory.action.title", {
smart_count: 1,
})}
confirmContent={translate("resources.room_directory.action.content", {
smart_count: 1,
})}
resource="room_directory"
icon={<FolderSharedIcon />}
/>
);
};
export const RoomDirectoryBulkDeleteButton = props => (
<BulkDeleteButton
{...props}
label="resources.room_directory.action.erase"
undoable={false}
confirmTitle="resources.room_directory.action.title"
confirmContent="resources.room_directory.action.content"
resource="room_directory"
icon={<FolderSharedIcon />}
/>
);
export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
const notify = useNotify();
const refresh = useRefresh();
const unselectAll = useUnselectAll();
const [createMany, { loading }] = useMutation();
const handleSend = values => {
createMany(
{
type: "createMany",
resource: "room_directory",
payload: { ids: selectedIds, data: {} },
},
{
onSuccess: ({ data }) => {
notify("resources.room_directory.action.send_success");
unselectAll("rooms");
refresh();
},
onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"),
}
);
};
return (
<Button
label="resources.room_directory.action.create"
onClick={handleSend}
disabled={loading}
>
<FolderSharedIcon />
</Button>
);
};
export const RoomDirectorySaveButton = ({ record }) => {
const notify = useNotify();
const refresh = useRefresh();
const [create, { loading }] = useCreate("room_directory");
const handleSend = values => {
create(
{
payload: { data: { id: record.id } },
},
{
onSuccess: ({ data }) => {
notify("resources.room_directory.action.send_success");
refresh();
},
onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"),
}
);
};
return (
<Button
label="resources.room_directory.action.create"
onClick={handleSend}
disabled={loading}
>
<FolderSharedIcon />
</Button>
);
};
const RoomDirectoryBulkActionButtons = props => (
<Fragment>
<RoomDirectoryBulkDeleteButton {...props} />
</Fragment>
);
const AvatarField = ({ source, className, record = {} }) => (
<Avatar src={record[source]} className={className} />
);
const RoomDirectoryFilter = ({ ...props }) => {
const translate = useTranslate();
return (
<Filter {...props}>
<Chip
label={translate("resources.rooms.fields.room_id")}
source="room_id"
defaultValue={false}
style={{ marginBottom: 8 }}
/>
<Chip
label={translate("resources.rooms.fields.topic")}
source="topic"
defaultValue={false}
style={{ marginBottom: 8 }}
/>
<Chip
label={translate("resources.rooms.fields.canonical_alias")}
source="canonical_alias"
defaultValue={false}
style={{ marginBottom: 8 }}
/>
</Filter>
);
};
export const FilterableRoomDirectoryList = ({ ...props }) => {
const classes = useStyles();
const translate = useTranslate();
const filter = props.roomDirectoryFilters;
const roomIdFilter = filter && filter.room_id ? true : false;
const topicFilter = filter && filter.topic ? true : false;
const canonicalAliasFilter = filter && filter.canonical_alias ? true : false;
return (
<List
{...props}
pagination={<RoomDirectoryPagination />}
bulkActionButtons={<RoomDirectoryBulkActionButtons />}
filters={<RoomDirectoryFilter />}
perPage={100}
>
<Datagrid>
<AvatarField
source="avatar_src"
sortable={false}
className={classes.small}
label={translate("resources.rooms.fields.avatar")}
/>
<TextField
source="name"
sortable={false}
label={translate("resources.rooms.fields.name")}
/>
{roomIdFilter && (
<TextField
source="room_id"
sortable={false}
label={translate("resources.rooms.fields.room_id")}
/>
)}
{canonicalAliasFilter && (
<TextField
source="canonical_alias"
sortable={false}
label={translate("resources.rooms.fields.canonical_alias")}
/>
)}
{topicFilter && (
<TextField
source="topic"
sortable={false}
label={translate("resources.rooms.fields.topic")}
/>
)}
<NumberField
source="num_joined_members"
sortable={false}
label={translate("resources.rooms.fields.joined_members")}
/>
<BooleanField
source="world_readable"
sortable={false}
label={translate("resources.room_directory.fields.world_readable")}
/>
<BooleanField
source="guest_can_join"
sortable={false}
label={translate("resources.room_directory.fields.guest_can_join")}
/>
</Datagrid>
</List>
);
};
function mapStateToProps(state) {
return {
roomDirectoryFilters:
state.admin.resources.room_directory.list.params.displayedFilters,
};
}
export const RoomDirectoryList = connect(mapStateToProps)(
FilterableRoomDirectoryList
);

View file

@ -2,7 +2,7 @@ import React, { Fragment } from "react";
import { connect } from "react-redux";
import {
BooleanField,
BulkDeleteWithConfirmButton,
BulkDeleteButton,
Datagrid,
DeleteButton,
Filter,
@ -27,6 +27,12 @@ import PageviewIcon from "@material-ui/icons/Pageview";
import UserIcon from "@material-ui/icons/Group";
import ViewListIcon from "@material-ui/icons/ViewList";
import VisibilityIcon from "@material-ui/icons/Visibility";
import {
RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton,
RoomDirectoryDeleteButton,
RoomDirectorySaveButton,
} from "./RoomDirectory";
const RoomPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@ -73,16 +79,26 @@ const RoomTitle = ({ record }) => {
};
const RoomShowActions = ({ basePath, data, resource }) => {
const translate = useTranslate();
var roomDirectoryStatus = "";
if (data) {
roomDirectoryStatus = data.public;
}
return (
<TopToolbar>
{roomDirectoryStatus === false && (
<RoomDirectorySaveButton record={data} />
)}
{roomDirectoryStatus === true && (
<RoomDirectoryDeleteButton record={data} />
)}
<DeleteButton
basePath={basePath}
record={data}
resource={resource}
undoable={false}
confirmTitle={translate("synapseadmin.rooms.delete.title")}
confirmContent={translate("synapseadmin.rooms.delete.message")}
mutationMode="pessimistic"
confirmTitle="resources.rooms.action.erase.title"
confirmContent="resources.rooms.action.erase.content"
/>
</TopToolbar>
);
@ -205,7 +221,14 @@ export const RoomShow = props => {
const RoomBulkActionButtons = props => (
<Fragment>
<BulkDeleteWithConfirmButton {...props} />
<RoomDirectoryBulkSaveButton {...props} />
<RoomDirectoryBulkDeleteButton {...props} />
<BulkDeleteButton
{...props}
confirmTitle="resources.rooms.action.erase.title"
confirmContent="resources.rooms.action.erase.content"
undoable={false}
/>
</Fragment>
);
@ -249,7 +272,6 @@ const FilterableRoomList = ({ ...props }) => {
const stateEventsFilter = filter && filter.state_events ? true : false;
const versionFilter = filter && filter.version ? true : false;
const federateableFilter = filter && filter.federatable ? true : false;
const translate = useTranslate();
return (
<List
@ -257,12 +279,7 @@ const FilterableRoomList = ({ ...props }) => {
pagination={<RoomPagination />}
sort={{ field: "name", order: "ASC" }}
filters={<RoomFilter />}
bulkActionButtons={
<RoomBulkActionButtons
confirmTitle={translate("synapseadmin.rooms.delete.title")}
confirmContent={translate("synapseadmin.rooms.delete.message")}
/>
}
bulkActionButtons={<RoomBulkActionButtons />}
>
<Datagrid rowClick="show">
<EncryptionField

View file

@ -139,19 +139,17 @@ const UserFilter = props => (
</Filter>
);
const UserBulkActionButtons = props => {
const translate = useTranslate();
return (
<Fragment>
<ServerNoticeBulkButton {...props} />
<BulkDeleteButton
{...props}
label="resources.users.action.erase"
title={translate("resources.users.helper.erase")}
/>
</Fragment>
);
};
const UserBulkActionButtons = props => (
<Fragment>
<ServerNoticeBulkButton {...props} />
<BulkDeleteButton
{...props}
label="resources.users.action.erase"
confirmTitle="resources.users.helper.erase"
undoable={false}
/>
</Fragment>
);
const AvatarField = ({ source, className, record = {} }) => (
<Avatar src={record[source]} className={className} />
@ -238,7 +236,10 @@ const UserEditToolbar = props => {
<SaveButton submitOnEnter={true} />
<DeleteButton
label="resources.users.action.erase"
title={translate("resources.users.helper.erase")}
confirmTitle={translate("resources.users.helper.erase", {
smart_count: 1,
})}
mutationMode="pessimistic"
/>
<ServerNoticeButton />
</Toolbar>
@ -453,7 +454,7 @@ export const UserEdit = props => {
<TextField source="upload_name" sortable={false} />
<TextField source="quarantined_by" sortable={false} />
<BooleanField source="safe_from_quarantine" sortable={false} />
<DeleteButton undoable={false} redirect={false} />
<DeleteButton mutationMode="pessimistic" redirect={false} />
</Datagrid>
</ReferenceManyField>
</FormTab>

View file

@ -23,11 +23,6 @@ export default {
detail: "Details",
permission: "Berechtigungen",
},
delete: {
title: "Raum löschen",
message:
"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" } },
},
@ -149,11 +144,13 @@ export default {
is_encrypted: "Verschlüsselt",
encryption: "Verschlüsselungs-Algorithmus",
federatable: "Fö­de­rierbar",
public: "Öffentlich",
public: "Sichtbar im Raumverzeichnis",
creator: "Ersteller",
join_rules: "Beitrittsregeln",
guest_access: "Gastzugriff",
history_visibility: "Historie-Sichtbarkeit",
topic: "Thema",
avatar: "Avatar",
},
enums: {
join_rules: {
@ -174,6 +171,13 @@ export default {
},
unencrypted: "Nicht verschlüsselt",
},
action: {
erase: {
title: "Raum löschen",
content:
"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: {
name: "Ereignisbericht |||| Ereignisberichte",
@ -291,6 +295,23 @@ export default {
media_length: "Größe der Dateien",
},
},
room_directory: {
name: "Raumverzeichnis",
fields: {
world_readable: "Gastbenutzer dürfen ohne Beitritt lesen",
guest_can_join: "Gastbenutzer dürfen beitreten",
},
action: {
title:
"Raum aus Verzeichnis löschen |||| %{smart_count} Räume aus Verzeichnis löschen",
content:
"Möchten Sie den Raum wirklich aus dem Raumverzeichnis löschen? |||| Möchten Sie die %{smart_count} Räume wirklich aus dem Raumverzeichnis löschen?",
erase: "Lösche aus Verzeichnis",
create: "Eintragen ins Verzeichnis",
send_success: "Raum erfolgreich eingetragen.",
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
},
},
},
ra: {
...germanMessages.ra,

View file

@ -22,11 +22,6 @@ export default {
detail: "Details",
permission: "Permissions",
},
delete: {
title: "Delete room",
message:
"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" } },
},
@ -147,11 +142,13 @@ export default {
is_encrypted: "Encrypted",
encryption: "Encryption",
federatable: "Federatable",
public: "Public",
public: "Visible in room directory",
creator: "Creator",
join_rules: "Join rules",
guest_access: "Guest access",
history_visibility: "History visibility",
topic: "Topic",
avatar: "Avatar",
},
enums: {
join_rules: {
@ -172,6 +169,11 @@ export default {
},
unencrypted: "Unencrypted",
},
erase: {
title: "Delete room",
content:
"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: {
name: "Reported event |||| Reported events",
@ -289,5 +291,22 @@ export default {
media_length: "Media length",
},
},
room_directory: {
name: "Room directory",
fields: {
world_readable: "guest users may view without joining",
guest_can_join: "guest users may join",
},
action: {
title:
"Delete room from directory |||| Delete %{smart_count} rooms from directory",
content:
"Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory",
erase: "Delete from room directory",
create: "Publish in room directory",
send_success: "Room successfully published.",
send_failure: "An error has occurred.",
},
},
},
};

View file

@ -195,6 +195,30 @@ const resourceMap = {
return json.total;
},
},
room_directory: {
path: "/_matrix/client/r0/publicRooms",
map: rd => ({
...rd,
id: rd.room_id,
public: !!rd.public,
guest_access: !!rd.guest_access,
avatar_src: mxcUrlToHttp(rd.avatar_url),
}),
data: "chunk",
total: json => {
return json.total_room_count_estimate;
},
create: params => ({
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
body: { visibility: "public" },
method: "PUT",
}),
delete: params => ({
endpoint: `/_matrix/client/r0/directory/list/room/${params.id}`,
body: { visibility: "private" },
method: "PUT",
}),
},
};
function filterNullValues(key, value) {