Merge remote-tracking branch 'synapse-admin/master' into post_upgrade

This commit is contained in:
dklimpel 2024-02-06 20:38:01 +01:00
commit 6077162d41
14 changed files with 278 additions and 283 deletions

View File

@ -8,26 +8,15 @@ import {
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, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports";
import users from "./components/users";
import rooms from "./components/rooms";
import userMediaStats from "./components/statistics";
import reports from "./components/EventReports";
import roomDirectory from "./components/RoomDirectory";
import destinations from "./components/destinations";
import registrationToken from "./components/RegistrationTokens";
import LoginPage from "./components/LoginPage";
import ConfirmationNumberIcon from "@mui/icons-material/ConfirmationNumber";
import CloudQueueIcon from "@mui/icons-material/CloudQueue";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import UserIcon from "@mui/icons-material/Group";
import { UserMediaStatsList } from "./components/statistics";
import RoomIcon from "@mui/icons-material/ViewList";
import ReportIcon from "@mui/icons-material/Warning";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import { DestinationList, DestinationShow } from "./components/destinations";
import { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
RegistrationTokenEdit,
RegistrationTokenList,
} from "./components/RegistrationTokens";
import { RoomDirectoryList } from "./components/RoomDirectory";
import { Route } from "react-router-dom";
import germanMessages from "./i18n/de";
import englishMessages from "./i18n/en";
@ -59,43 +48,13 @@ const App = () => (
<CustomRoutes>
<Route path="/import_users" element={<ImportFeature />} />
</CustomRoutes>
<Resource
name="users"
list={UserList}
create={UserCreate}
edit={UserEdit}
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="room_directory"
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
<Resource
name="destinations"
list={DestinationList}
show={DestinationShow}
icon={CloudQueueIcon}
/>
<Resource
name="registration_tokens"
list={RegistrationTokenList}
create={RegistrationTokenCreate}
edit={RegistrationTokenEdit}
icon={ConfirmationNumberIcon}
/>
<Resource {...users} />
<Resource {...rooms} />
<Resource {...userMediaStats} />
<Resource {...reports} />
<Resource {...roomDirectory} />
<Resource {...destinations} />
<Resource {...registrationToken} />
<Resource name="connections" />
<Resource name="devices" />
<Resource name="room_members" />

View File

@ -13,6 +13,7 @@ import {
useTranslate,
} from "react-admin";
import PageviewIcon from "@mui/icons-material/Pageview";
import ReportIcon from "@mui/icons-material/Warning";
import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = {
@ -98,24 +99,31 @@ export const ReportShow = props => {
);
};
export const ReportList = () => {
return (
<List
pagination={<ReportPagination />}
sort={{ field: "received_ts", order: "DESC" }}
>
<Datagrid rowClick="show" bulkActionButtons={false}>
<TextField source="id" sortable={false} />
<DateField
source="received_ts"
showTime
options={date_format}
sortable={true}
/>
<TextField sortable={false} source="user_id" />
<TextField sortable={false} source="name" />
<TextField sortable={false} source="score" />
</Datagrid>
</List>
);
export const ReportList = () => (
<List
pagination={<ReportPagination />}
sort={{ field: "received_ts", order: "DESC" }}
>
<Datagrid rowClick="show" bulkActionButtons={false}>
<TextField source="id" sortable={false} />
<DateField
source="received_ts"
showTime
options={date_format}
sortable={true}
/>
<TextField sortable={false} source="user_id" />
<TextField sortable={false} source="name" />
<TextField sortable={false} source="score" />
</Datagrid>
</List>
);
const resource = {
name: "reports",
icon: ReportIcon,
list: ReportList,
show: ReportShow,
};
export default resource;

View File

@ -32,7 +32,7 @@ function TranslatableOption({ value, text }) {
return <option value={value}>{translate(text)}</option>;
}
const FilePicker = props => {
const FilePicker = () => {
const [values, setValues] = useState(null);
const [error, setError] = useState(null);
const [stats, setStats] = useState(null);
@ -191,7 +191,7 @@ const FilePicker = props => {
return true;
};
const runImport = async e => {
const runImport = async _e => {
if (progress !== null) {
notify("import_users.errors.already_in_progress");
return;
@ -307,7 +307,7 @@ const FilePicker = props => {
let retries = 0;
const submitRecord = recordData => {
return dataProvider.getOne("users", { id: recordData.id }).then(
async alreadyExists => {
async _alreadyExists => {
if (LOGGING) console.log("already existed");
if (useridMode === "update" || conflictMode === "skip") {
@ -332,7 +332,7 @@ const FilePicker = props => {
}
}
},
async okToSubmit => {
async _okToSubmit => {
if (LOGGING)
console.log(
"OK to create record " +

View File

@ -307,9 +307,13 @@ const LoginPage = () => {
<FormBox>
<Card className="card">
<Box className="avatar">
<Avatar className="icon">
<LockIcon />
</Avatar>
{loading ? (
<CircularProgress size={25} thickness={2} />
) : (
<Avatar className="icon">
<LockIcon />
</Avatar>
)}
</Box>
<Box className="hint">{translate("synapseadmin.auth.welcome")}</Box>
<Box className="form">
@ -339,7 +343,6 @@ const LoginPage = () => {
disabled={loading || !supportPassAuth}
fullWidth
>
{loading && <CircularProgress size={25} thickness={2} />}
{translate("ra.auth.sign_in")}
</Button>
<Button
@ -349,7 +352,6 @@ const LoginPage = () => {
disabled={loading || ssoBaseUrl === ""}
fullWidth
>
{loading && <CircularProgress size={25} thickness={2} />}
{translate("synapseadmin.auth.sso_sign_in")}
</Button>
</CardActions>

View File

@ -6,7 +6,6 @@ import {
DateField,
DateTimeInput,
Edit,
Filter,
List,
maxValue,
number,
@ -19,6 +18,7 @@ import {
TextField,
Toolbar,
} from "react-admin";
import RegistrationTokenIcon from "@mui/icons-material/ConfirmationNumber";
const date_format = {
year: "numeric",
@ -54,36 +54,30 @@ const dateFormatter = v => {
return `${year}-${month}-${day}T${hour}:${minute}`;
};
const RegistrationTokenFilter = props => (
<Filter {...props}>
<BooleanInput source="valid" alwaysOn />
</Filter>
);
const registrationTokenFilters = [<BooleanInput source="valid" alwaysOn />];
export const RegistrationTokenList = props => {
return (
<List
{...props}
filters={<RegistrationTokenFilter />}
filterDefaultValues={{ valid: true }}
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<TextField source="token" sortable={false} />
<NumberField source="uses_allowed" sortable={false} />
<NumberField source="pending" sortable={false} />
<NumberField source="completed" sortable={false} />
<DateField
source="expiry_time"
showTime
options={date_format}
sortable={false}
/>
</Datagrid>
</List>
);
};
export const RegistrationTokenList = props => (
<List
{...props}
filters={registrationTokenFilters}
filterDefaultValues={{ valid: true }}
pagination={false}
perPage={500}
>
<Datagrid rowClick="edit">
<TextField source="token" sortable={false} />
<NumberField source="uses_allowed" sortable={false} />
<NumberField source="pending" sortable={false} />
<NumberField source="completed" sortable={false} />
<DateField
source="expiry_time"
showTime
options={date_format}
sortable={false}
/>
</Datagrid>
</List>
);
export const RegistrationTokenCreate = props => (
<Create redirect="list">
@ -117,24 +111,32 @@ export const RegistrationTokenCreate = props => (
</Create>
);
export const RegistrationTokenEdit = props => {
return (
<Edit>
<SimpleForm>
<TextInput source="token" disabled />
<NumberInput source="pending" disabled />
<NumberInput source="completed" disabled />
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput
source="expiry_time"
parse={dateParser}
format={dateFormatter}
/>
</SimpleForm>
</Edit>
);
export const RegistrationTokenEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="token" disabled />
<NumberInput source="pending" disabled />
<NumberInput source="completed" disabled />
<NumberInput
source="uses_allowed"
validate={validateUsesAllowed}
step={1}
/>
<DateTimeInput
source="expiry_time"
parse={dateParser}
format={dateFormatter}
/>
</SimpleForm>
</Edit>
);
const resource = {
name: "users",
icon: RegistrationTokenIcon,
list: RegistrationTokenList,
edit: RegistrationTokenEdit,
create: RegistrationTokenCreate,
};
export default resource;

View File

@ -1,5 +1,4 @@
import React, { Fragment } from "react";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import React from "react";
import {
BooleanField,
BulkDeleteButton,
@ -23,6 +22,7 @@ import {
useUnselectAll,
} from "react-admin";
import { useMutation } from "react-query";
import RoomDirectoryIcon from "@mui/icons-material/FolderShared";
import AvatarField from "./AvatarField";
const RoomDirectoryPagination = () => (
@ -44,7 +44,7 @@ export const RoomDirectoryDeleteButton = () => {
smart_count: 1,
})}
resource="room_directory"
icon={<FolderSharedIcon />}
icon={<RoomDirectoryIcon />}
/>
);
};
@ -56,7 +56,7 @@ export const RoomDirectoryBulkDeleteButton = () => (
confirmTitle="resources.room_directory.action.title"
confirmContent="resources.room_directory.action.content"
resource="room_directory"
icon={<FolderSharedIcon />}
icon={<RoomDirectoryIcon />}
/>
);
@ -91,7 +91,7 @@ export const RoomDirectoryBulkSaveButton = () => {
onClick={mutate}
disabled={isloading}
>
<FolderSharedIcon />
<RoomDirectoryIcon />
</Button>
);
};
@ -102,16 +102,16 @@ export const RoomDirectorySaveButton = () => {
const refresh = useRefresh();
const [create, { isloading }] = useCreate();
const handleSend = values => {
const handleSend = () => {
create(
"room_directory",
{ data: { id: record.id } },
{
onSuccess: () => {
onSuccess: _data => {
notify("resources.room_directory.action.send_success");
refresh();
},
onError: () =>
onError: _error =>
notify("resources.room_directory.action.send_failure", {
type: "error",
}),
@ -125,16 +125,12 @@ export const RoomDirectorySaveButton = () => {
onClick={handleSend}
disabled={isloading}
>
<FolderSharedIcon />
<RoomDirectoryIcon />
</Button>
);
};
const RoomDirectoryBulkActionButtons = () => (
<Fragment>
<RoomDirectoryBulkDeleteButton />
</Fragment>
);
const RoomDirectoryBulkActionButtons = () => <RoomDirectoryBulkDeleteButton />;
const RoomDirectoryListActions = () => (
<TopToolbar>
@ -198,3 +194,11 @@ export const RoomDirectoryList = () => (
</DatagridConfigurable>
</List>
);
const resource = {
name: "room_directory",
icon: RoomDirectoryIcon,
list: RoomDirectoryList,
};
export default resource;

View File

@ -1,4 +1,4 @@
import React, { Fragment, useState } from "react";
import React, { useState } from "react";
import {
Button,
SaveButton,
@ -91,7 +91,7 @@ export const ServerNoticeButton = () => {
};
return (
<Fragment>
<>
<Button
label="resources.servernotices.send"
onClick={handleDialogOpen}
@ -104,7 +104,7 @@ export const ServerNoticeButton = () => {
onClose={handleDialogClose}
onSubmit={handleSend}
/>
</Fragment>
</>
);
};
@ -137,7 +137,7 @@ export const ServerNoticeBulkButton = () => {
);
return (
<Fragment>
<>
<Button
label="resources.servernotices.send"
onClick={handleDialogOpen}
@ -150,6 +150,6 @@ export const ServerNoticeBulkButton = () => {
onClose={handleDialogClose}
onSubmit={mutate}
/>
</Fragment>
</>
);
};

View File

@ -3,7 +3,6 @@ import {
Button,
Datagrid,
DateField,
Filter,
List,
Pagination,
ReferenceField,
@ -21,6 +20,7 @@ import {
useTranslate,
} from "react-admin";
import AutorenewIcon from "@mui/icons-material/Autorenew";
import DestinationsIcon from "@mui/icons-material/CloudQueue";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import ViewListIcon from "@mui/icons-material/ViewList";
@ -41,15 +41,9 @@ const destinationRowSx = (record, _index) => ({
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
});
const DestinationFilter = props => {
return (
<Filter {...props}>
<SearchInput source="destination" alwaysOn />
</Filter>
);
};
const destinationFilters = [<SearchInput source="destination" alwaysOn />];
export const DestinationReconnectButton = props => {
export const DestinationReconnectButton = () => {
const record = useRecordContext();
const refresh = useRefresh();
const notify = useNotify();
@ -64,7 +58,7 @@ export const DestinationReconnectButton = props => {
handleReconnect(
"destinations",
{ id: record.id, previousData: record },
{ id: record.id },
{
onSuccess: () => {
notify("ra.notification.updated", {
@ -90,7 +84,7 @@ export const DestinationReconnectButton = props => {
);
};
const DestinationShowActions = props => (
const DestinationShowActions = () => (
<TopToolbar>
<DestinationReconnectButton />
</TopToolbar>
@ -109,7 +103,7 @@ const DestinationTitle = () => {
export const DestinationList = () => {
return (
<List
filters={<DestinationFilter />}
filters={destinationFilters}
pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }}
>
@ -179,3 +173,12 @@ export const DestinationShow = () => {
</Show>
);
};
const resource = {
name: "destinations",
icon: DestinationsIcon,
list: DestinationList,
show: DestinationShow,
};
export default resource;

View File

@ -1,4 +1,4 @@
import React, { Fragment, useState } from "react";
import React, { useState } from "react";
import {
Button,
useDelete,
@ -25,10 +25,9 @@ export const DeviceRemoveButton = () => {
const handleDialogClose = () => setOpen(false);
const handleConfirm = () => {
console.log(record);
removeDevice(
"devices",
{ id: record.id, previousData: record },
{ id: record.id, meta: { user_id: record.user_id } },
{
onSuccess: () => {
notify("resources.devices.action.erase.success");
@ -43,8 +42,9 @@ export const DeviceRemoveButton = () => {
};
return (
<Fragment>
<>
<Button
{...props}
label="ra.action.remove"
onClick={handleClick}
sx={{
@ -72,6 +72,6 @@ export const DeviceRemoveButton = () => {
name: record.display_name ? record.display_name : record.id,
}}
/>
</Fragment>
</>
);
};

View File

@ -1,4 +1,4 @@
import React, { Fragment, useState } from "react";
import React, { useState } from "react";
import {
BooleanInput,
Button,
@ -99,10 +99,9 @@ export const DeleteMediaButton = () => {
const handleDialogClose = () => setOpen(false);
const handleSend = data => {
console.log({ ...data });
deleteOne(
"delete_media",
{ previousData: { ...data } },
{ id: data.id, meta: { ...data } },
{
onSuccess: () => {
notify("resources.delete_media.action.send_success");
@ -117,8 +116,9 @@ export const DeleteMediaButton = () => {
};
return (
<Fragment>
<>
<Button
{...props}
label="resources.delete_media.action.send"
onClick={handleDialogOpen}
disabled={isLoading}
@ -140,7 +140,7 @@ export const DeleteMediaButton = () => {
onClose={handleDialogClose}
onSubmit={handleSend}
/>
</Fragment>
</>
);
};
@ -149,7 +149,7 @@ export const ProtectMediaButton = () => {
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
const [create, { loading }] = useCreate();
const [create, { isLoading }] = useCreate();
const [deleteOne] = useDelete();
if (!record) return null;
@ -174,7 +174,7 @@ export const ProtectMediaButton = () => {
const handleUnprotect = () => {
deleteOne(
"protect_media",
{ id: record.id, previousData: record },
{ id: record.id },
{
onSuccess: () => {
notify("resources.protect_media.action.send_success");
@ -193,7 +193,7 @@ export const ProtectMediaButton = () => {
Wrapping Tooltip with <div>
https://github.com/marmelab/react-admin/issues/4349#issuecomment-578594735
*/
<Fragment>
<>
{record.quarantined_by && (
<Tooltip
title={translate("resources.protect_media.action.none", {
@ -219,7 +219,7 @@ export const ProtectMediaButton = () => {
arrow
>
<div>
<Button onClick={handleUnprotect} disabled={loading}>
<Button onClick={handleUnprotect} disabled={isLoading}>
<LockIcon />
</Button>
</div>
@ -232,13 +232,13 @@ export const ProtectMediaButton = () => {
})}
>
<div>
<Button onClick={handleProtect} disabled={loading}>
<Button onClick={handleProtect} disabled={isLoading}>
<LockOpenIcon />
</Button>
</div>
</Tooltip>
)}
</Fragment>
</>
);
};
@ -247,7 +247,7 @@ export const QuarantineMediaButton = () => {
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
const [create, { loading }] = useCreate();
const [create, { isLoading }] = useCreate();
const [deleteOne] = useDelete();
if (!record) return null;
@ -272,7 +272,7 @@ export const QuarantineMediaButton = () => {
const handleRemoveQuarantaine = () => {
deleteOne(
"quarantine_media",
{ id: record.id, previousData: record },
{ id: record.id },
{
onSuccess: () => {
notify("resources.quarantine_media.action.send_success");
@ -287,7 +287,7 @@ export const QuarantineMediaButton = () => {
};
return (
<Fragment>
<>
{record.safe_from_quarantine && (
<Tooltip
title={translate("resources.quarantine_media.action.none", {
@ -295,7 +295,7 @@ export const QuarantineMediaButton = () => {
})}
>
<div>
<Button disabled={true}>
<Button {...props} disabled={true}>
<ClearIcon />
</Button>
</div>
@ -308,7 +308,11 @@ export const QuarantineMediaButton = () => {
})}
>
<div>
<Button onClick={handleRemoveQuarantaine} disabled={loading}>
<Button
{...props}
onClick={handleRemoveQuarantaine}
disabled={isLoading}
>
<BlockIcon color="error" />
</Button>
</div>
@ -321,12 +325,12 @@ export const QuarantineMediaButton = () => {
})}
>
<div>
<Button onClick={handleQuarantaine} disabled={loading}>
<Button onClick={handleQuarantaine} disabled={isLoading}>
<BlockIcon />
</Button>
</div>
</Tooltip>
)}
</Fragment>
</>
);
};

View File

@ -1,4 +1,4 @@
import React, { Fragment } from "react";
import React from "react";
import {
BooleanField,
BulkDeleteButton,
@ -7,7 +7,6 @@ import {
DatagridConfigurable,
DeleteButton,
ExportButton,
Filter,
FunctionField,
List,
NumberField,
@ -35,6 +34,7 @@ import UserIcon from "@mui/icons-material/Group";
import ViewListIcon from "@mui/icons-material/ViewList";
import VisibilityIcon from "@mui/icons-material/Visibility";
import EventIcon from "@mui/icons-material/Event";
import RoomIcon from "@mui/icons-material/ViewList";
import {
RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton,
@ -274,7 +274,7 @@ export const RoomShow = () => {
};
const RoomBulkActionButtons = () => (
<Fragment>
<>
<RoomDirectoryBulkSaveButton />
<RoomDirectoryBulkDeleteButton />
<BulkDeleteButton
@ -282,14 +282,10 @@ const RoomBulkActionButtons = () => (
confirmContent="resources.rooms.action.erase.content"
mutationMode="pessimistic"
/>
</Fragment>
</>
);
const RoomFilter = props => (
<Filter {...props}>
<SearchInput source="search_term" alwaysOn />
</Filter>
);
const roomFilters = [<SearchInput source="search_term" alwaysOn />];
const RoomListActions = () => (
<TopToolbar>
@ -298,14 +294,15 @@ const RoomListActions = () => (
</TopToolbar>
);
export const RoomList = () => {
export const RoomList = props => {
const theme = useTheme();
return (
<List
{...props}
pagination={<RoomPagination />}
sort={{ field: "name", order: "ASC" }}
filters={<RoomFilter />}
filters={roomFilters}
actions={<RoomListActions />}
>
<DatagridConfigurable
@ -345,3 +342,12 @@ export const RoomList = () => {
</List>
);
};
const resource = {
name: "rooms",
icon: RoomIcon,
list: RoomList,
show: RoomShow,
};
export default resource;

View File

@ -3,7 +3,6 @@ import { cloneElement } from "react";
import {
Datagrid,
ExportButton,
Filter,
List,
NumberField,
Pagination,
@ -13,6 +12,7 @@ import {
TopToolbar,
useListContext,
} from "react-admin";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import { DeleteMediaButton } from "./media";
const ListActions = props => {
@ -45,32 +45,34 @@ const UserMediaStatsPagination = () => (
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
const UserMediaStatsFilter = props => (
<Filter {...props}>
<SearchInput source="search_term" alwaysOn />
</Filter>
const userMediaStatsFilters = [<SearchInput source="search_term" alwaysOn />];
export const UserMediaStatsList = () => (
<List
actions={<ListActions />}
filters={userMediaStatsFilters}
pagination={<UserMediaStatsPagination />}
sort={{ field: "media_length", order: "DESC" }}
>
<Datagrid
rowClick={(id, resource, record) => "/users/" + id + "/media"}
bulkActionButtons={false}
>
<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>
);
export const UserMediaStatsList = props => {
return (
<List
actions={<ListActions />}
filters={<UserMediaStatsFilter />}
pagination={<UserMediaStatsPagination />}
sort={{ field: "media_length", order: "DESC" }}
>
<Datagrid
rowClick={(id, resource, record) => "/users/" + id + "/media"}
bulkActionButtons={false}
>
<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>
);
const resource = {
name: "user_media_statistics",
icon: EqualizerIcon,
list: UserMediaStatsList,
};
export default resource;

View File

@ -1,4 +1,4 @@
import React, { cloneElement, Fragment } from "react";
import React, { cloneElement } from "react";
import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
import ContactMailIcon from "@mui/icons-material/ContactMail";
import DevicesIcon from "@mui/icons-material/Devices";
@ -7,6 +7,7 @@ import NotificationsIcon from "@mui/icons-material/Notifications";
import PermMediaIcon from "@mui/icons-material/PermMedia";
import PersonPinIcon from "@mui/icons-material/PersonPin";
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
import UserIcon from "@mui/icons-material/Group";
import ViewListIcon from "@mui/icons-material/ViewList";
import {
ArrayInput,
@ -17,7 +18,6 @@ import {
Create,
Edit,
List,
Filter,
Toolbar,
SimpleForm,
SimpleFormIterator,
@ -125,60 +125,55 @@ const UserPagination = () => (
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
const UserFilter = props => (
<Filter {...props}>
<SearchInput source="name" alwaysOn />
<BooleanInput source="guests" alwaysOn />
<BooleanInput
label="resources.users.fields.show_deactivated"
source="deactivated"
alwaysOn
/>
</Filter>
);
const userFilters = [
<SearchInput source="name" alwaysOn />,
<BooleanInput source="guests" alwaysOn />,
<BooleanInput
label="resources.users.fields.show_deactivated"
source="deactivated"
alwaysOn
/>,
];
const UserBulkActionButtons = props => (
<Fragment>
<ServerNoticeBulkButton {...props} />
const UserBulkActionButtons = () => (
<>
<ServerNoticeBulkButton />
<BulkDeleteButton
{...props}
label="resources.users.action.erase"
confirmTitle="resources.users.helper.erase"
mutationMode="pessimistic"
/>
</Fragment>
</>
);
export const UserList = props => {
return (
<List
filters={<UserFilter />}
filterDefaultValues={{ guests: true, deactivated: false }}
sort={{ field: "name", order: "ASC" }}
actions={<UserListActions maxResults={10000} />}
pagination={<UserPagination />}
>
<Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}>
<AvatarField
source="avatar_src"
sx={{ height: "40px", width: "40px" }}
sortBy="avatar_url"
/>
<TextField source="id" sortBy="name" />
<TextField source="displayname" />
<BooleanField source="is_guest" />
<BooleanField source="admin" />
<BooleanField source="deactivated" />
<DateField
source="creation_ts"
label="resources.users.fields.creation_ts_ms"
showTime
options={date_format}
/>
</Datagrid>
</List>
);
};
export const UserList = () => (
<List
filters={userFilters}
filterDefaultValues={{ guests: true, deactivated: false }}
sort={{ field: "name", order: "ASC" }}
actions={<UserListActions maxResults={10000} />}
pagination={<UserPagination />}
>
<Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}>
<AvatarField
source="avatar_src"
sx={{ height: "40px", width: "40px" }}
sortBy="avatar_url"
/>
<TextField source="id" sortBy="name" />
<TextField source="displayname" />
<BooleanField source="is_guest" />
<BooleanField source="admin" />
<BooleanField source="deactivated" />
<DateField
source="creation_ts"
label="resources.users.fields.creation_ts_ms"
showTime
options={date_format}
/>
</Datagrid>
</List>
);
// https://matrix.org/docs/spec/appendices#user-identifiers
// here only local part of user_id
@ -302,7 +297,7 @@ export const UserCreate = props => (
</Create>
);
const UserTitle = props => {
const UserTitle = () => {
const record = useRecordContext();
const translate = useTranslate();
return (
@ -529,3 +524,13 @@ export const UserEdit = props => {
</Edit>
);
};
const resource = {
name: "users",
icon: UserIcon,
list: UserList,
edit: UserEdit,
create: UserCreate,
};
export default resource;

View File

@ -98,7 +98,7 @@ const resourceMap = {
}),
delete: params => ({
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
params.user_id
params.meta.user_id
)}/devices/${params.id}`,
}),
},
@ -184,9 +184,9 @@ const resourceMap = {
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}`,
)}/delete?before_ts=${params.meta.before_ts}&size_gt=${
params.meta.size_gt
}&keep_profiles=${params.meta.keep_profiles}`,
method: "POST",
}),
},
@ -197,7 +197,7 @@ const resourceMap = {
method: "POST",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/media/unprotect/${params.media_id}`,
endpoint: `/_synapse/admin/v1/media/unprotect/${params.id}`,
method: "POST",
}),
},
@ -212,7 +212,7 @@ const resourceMap = {
delete: params => ({
endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem(
"home_server"
)}/${params.media_id}`,
)}/${params.id}`,
method: "POST",
}),
},
@ -534,7 +534,7 @@ const dataProvider = {
const res = resourceMap[resource];
if ("delete" in res) {
const del = res["delete"](params.previousData);
const del = res["delete"](params);
const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, {
method: "method" in del ? del.method : "DELETE",
@ -546,7 +546,7 @@ const dataProvider = {
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`, {
method: "DELETE",
body: JSON.stringify(params.previousData, filterNullValues),
body: JSON.stringify(params, filterNullValues),
}).then(({ json }) => ({
data: json,
}));