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 { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@material-ui/icons/ViewList"; import RoomIcon from "@material-ui/icons/ViewList";
import ReportIcon from "@material-ui/icons/Warning"; import ReportIcon from "@material-ui/icons/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import { ImportFeature } from "./components/ImportFeature"; import { ImportFeature } from "./components/ImportFeature";
import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import germanMessages from "./i18n/de"; import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en"; import englishMessages from "./i18n/en";
@ -57,6 +59,11 @@ const App = () => (
show={ReportShow} show={ReportShow}
icon={ReportIcon} icon={ReportIcon}
/> />
<Resource
name="room_directory"
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
<Resource name="connections" /> <Resource name="connections" />
<Resource name="devices" /> <Resource name="devices" />
<Resource name="room_members" /> <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 { connect } from "react-redux";
import { import {
BooleanField, BooleanField,
BulkDeleteWithConfirmButton, BulkDeleteButton,
Datagrid, Datagrid,
DeleteButton, DeleteButton,
Filter, Filter,
@ -27,6 +27,12 @@ import PageviewIcon from "@material-ui/icons/Pageview";
import UserIcon from "@material-ui/icons/Group"; import UserIcon from "@material-ui/icons/Group";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@material-ui/icons/ViewList";
import VisibilityIcon from "@material-ui/icons/Visibility"; import VisibilityIcon from "@material-ui/icons/Visibility";
import {
RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton,
RoomDirectoryDeleteButton,
RoomDirectorySaveButton,
} from "./RoomDirectory";
const RoomPagination = props => ( const RoomPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
@ -73,16 +79,26 @@ const RoomTitle = ({ record }) => {
}; };
const RoomShowActions = ({ basePath, data, resource }) => { const RoomShowActions = ({ basePath, data, resource }) => {
const translate = useTranslate(); var roomDirectoryStatus = "";
if (data) {
roomDirectoryStatus = data.public;
}
return ( return (
<TopToolbar> <TopToolbar>
{roomDirectoryStatus === false && (
<RoomDirectorySaveButton record={data} />
)}
{roomDirectoryStatus === true && (
<RoomDirectoryDeleteButton record={data} />
)}
<DeleteButton <DeleteButton
basePath={basePath} basePath={basePath}
record={data} record={data}
resource={resource} resource={resource}
undoable={false} mutationMode="pessimistic"
confirmTitle={translate("synapseadmin.rooms.delete.title")} confirmTitle="resources.rooms.action.erase.title"
confirmContent={translate("synapseadmin.rooms.delete.message")} confirmContent="resources.rooms.action.erase.content"
/> />
</TopToolbar> </TopToolbar>
); );
@ -205,7 +221,14 @@ export const RoomShow = props => {
const RoomBulkActionButtons = props => ( const RoomBulkActionButtons = props => (
<Fragment> <Fragment>
<BulkDeleteWithConfirmButton {...props} /> <RoomDirectoryBulkSaveButton {...props} />
<RoomDirectoryBulkDeleteButton {...props} />
<BulkDeleteButton
{...props}
confirmTitle="resources.rooms.action.erase.title"
confirmContent="resources.rooms.action.erase.content"
undoable={false}
/>
</Fragment> </Fragment>
); );
@ -249,7 +272,6 @@ const FilterableRoomList = ({ ...props }) => {
const stateEventsFilter = filter && filter.state_events ? true : false; const stateEventsFilter = filter && filter.state_events ? true : false;
const versionFilter = filter && filter.version ? true : false; const versionFilter = filter && filter.version ? true : false;
const federateableFilter = filter && filter.federatable ? true : false; const federateableFilter = filter && filter.federatable ? true : false;
const translate = useTranslate();
return ( return (
<List <List
@ -257,12 +279,7 @@ const FilterableRoomList = ({ ...props }) => {
pagination={<RoomPagination />} pagination={<RoomPagination />}
sort={{ field: "name", order: "ASC" }} sort={{ field: "name", order: "ASC" }}
filters={<RoomFilter />} filters={<RoomFilter />}
bulkActionButtons={ bulkActionButtons={<RoomBulkActionButtons />}
<RoomBulkActionButtons
confirmTitle={translate("synapseadmin.rooms.delete.title")}
confirmContent={translate("synapseadmin.rooms.delete.message")}
/>
}
> >
<Datagrid rowClick="show"> <Datagrid rowClick="show">
<EncryptionField <EncryptionField

View file

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

View file

@ -23,11 +23,6 @@ export default {
detail: "Details", detail: "Details",
permission: "Berechtigungen", 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" } }, reports: { tabs: { basic: "Allgemein", detail: "Details" } },
}, },
@ -149,11 +144,13 @@ export default {
is_encrypted: "Verschlüsselt", is_encrypted: "Verschlüsselt",
encryption: "Verschlüsselungs-Algorithmus", encryption: "Verschlüsselungs-Algorithmus",
federatable: "Fö­de­rierbar", federatable: "Fö­de­rierbar",
public: "Öffentlich", public: "Sichtbar im Raumverzeichnis",
creator: "Ersteller", creator: "Ersteller",
join_rules: "Beitrittsregeln", join_rules: "Beitrittsregeln",
guest_access: "Gastzugriff", guest_access: "Gastzugriff",
history_visibility: "Historie-Sichtbarkeit", history_visibility: "Historie-Sichtbarkeit",
topic: "Thema",
avatar: "Avatar",
}, },
enums: { enums: {
join_rules: { join_rules: {
@ -174,6 +171,13 @@ export default {
}, },
unencrypted: "Nicht verschlüsselt", 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: { reports: {
name: "Ereignisbericht |||| Ereignisberichte", name: "Ereignisbericht |||| Ereignisberichte",
@ -291,6 +295,23 @@ export default {
media_length: "Größe der Dateien", 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: { ra: {
...germanMessages.ra, ...germanMessages.ra,

View file

@ -22,11 +22,6 @@ export default {
detail: "Details", detail: "Details",
permission: "Permissions", 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" } }, reports: { tabs: { basic: "Basic", detail: "Details" } },
}, },
@ -147,11 +142,13 @@ export default {
is_encrypted: "Encrypted", is_encrypted: "Encrypted",
encryption: "Encryption", encryption: "Encryption",
federatable: "Federatable", federatable: "Federatable",
public: "Public", public: "Visible in room directory",
creator: "Creator", creator: "Creator",
join_rules: "Join rules", join_rules: "Join rules",
guest_access: "Guest access", guest_access: "Guest access",
history_visibility: "History visibility", history_visibility: "History visibility",
topic: "Topic",
avatar: "Avatar",
}, },
enums: { enums: {
join_rules: { join_rules: {
@ -172,6 +169,11 @@ export default {
}, },
unencrypted: "Unencrypted", 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: { reports: {
name: "Reported event |||| Reported events", name: "Reported event |||| Reported events",
@ -289,5 +291,22 @@ export default {
media_length: "Media length", 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; 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) { function filterNullValues(key, value) {