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 polyglotI18nProvider from "ra-i18n-polyglot";
import authProvider from "./synapse/authProvider"; import authProvider from "./synapse/authProvider";
import dataProvider from "./synapse/dataProvider"; import dataProvider from "./synapse/dataProvider";
import { UserList, UserCreate, UserEdit } from "./components/users"; import users from "./components/users";
import { RoomList, RoomShow } from "./components/rooms"; import rooms from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports"; 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 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 { ImportFeature } from "./components/ImportFeature";
import {
RegistrationTokenCreate,
RegistrationTokenEdit,
RegistrationTokenList,
} from "./components/RegistrationTokens";
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";
@ -59,43 +48,13 @@ const App = () => (
<CustomRoutes> <CustomRoutes>
<Route path="/import_users" element={<ImportFeature />} /> <Route path="/import_users" element={<ImportFeature />} />
</CustomRoutes> </CustomRoutes>
<Resource <Resource {...users} />
name="users" <Resource {...rooms} />
list={UserList} <Resource {...userMediaStats} />
create={UserCreate} <Resource {...reports} />
edit={UserEdit} <Resource {...roomDirectory} />
icon={UserIcon} <Resource {...destinations} />
/> <Resource {...registrationToken} />
<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 name="connections" /> <Resource name="connections" />
<Resource name="devices" /> <Resource name="devices" />
<Resource name="room_members" /> <Resource name="room_members" />

View File

@ -13,6 +13,7 @@ import {
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import PageviewIcon from "@mui/icons-material/Pageview"; import PageviewIcon from "@mui/icons-material/Pageview";
import ReportIcon from "@mui/icons-material/Warning";
import ViewListIcon from "@mui/icons-material/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = { const date_format = {
@ -98,24 +99,31 @@ export const ReportShow = props => {
); );
}; };
export const ReportList = () => { export const ReportList = () => (
return ( <List
<List pagination={<ReportPagination />}
pagination={<ReportPagination />} sort={{ field: "received_ts", order: "DESC" }}
sort={{ field: "received_ts", order: "DESC" }} >
> <Datagrid rowClick="show" bulkActionButtons={false}>
<Datagrid rowClick="show" bulkActionButtons={false}> <TextField source="id" sortable={false} />
<TextField source="id" sortable={false} /> <DateField
<DateField source="received_ts"
source="received_ts" showTime
showTime options={date_format}
options={date_format} sortable={true}
sortable={true} />
/> <TextField sortable={false} source="user_id" />
<TextField sortable={false} source="user_id" /> <TextField sortable={false} source="name" />
<TextField sortable={false} source="name" /> <TextField sortable={false} source="score" />
<TextField sortable={false} source="score" /> </Datagrid>
</Datagrid> </List>
</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>; return <option value={value}>{translate(text)}</option>;
} }
const FilePicker = props => { const FilePicker = () => {
const [values, setValues] = useState(null); const [values, setValues] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [stats, setStats] = useState(null); const [stats, setStats] = useState(null);
@ -191,7 +191,7 @@ const FilePicker = props => {
return true; return true;
}; };
const runImport = async e => { const runImport = async _e => {
if (progress !== null) { if (progress !== null) {
notify("import_users.errors.already_in_progress"); notify("import_users.errors.already_in_progress");
return; return;
@ -307,7 +307,7 @@ const FilePicker = props => {
let retries = 0; let retries = 0;
const submitRecord = recordData => { const submitRecord = recordData => {
return dataProvider.getOne("users", { id: recordData.id }).then( return dataProvider.getOne("users", { id: recordData.id }).then(
async alreadyExists => { async _alreadyExists => {
if (LOGGING) console.log("already existed"); if (LOGGING) console.log("already existed");
if (useridMode === "update" || conflictMode === "skip") { if (useridMode === "update" || conflictMode === "skip") {
@ -332,7 +332,7 @@ const FilePicker = props => {
} }
} }
}, },
async okToSubmit => { async _okToSubmit => {
if (LOGGING) if (LOGGING)
console.log( console.log(
"OK to create record " + "OK to create record " +

View File

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

View File

@ -6,7 +6,6 @@ import {
DateField, DateField,
DateTimeInput, DateTimeInput,
Edit, Edit,
Filter,
List, List,
maxValue, maxValue,
number, number,
@ -19,6 +18,7 @@ import {
TextField, TextField,
Toolbar, Toolbar,
} from "react-admin"; } from "react-admin";
import RegistrationTokenIcon from "@mui/icons-material/ConfirmationNumber";
const date_format = { const date_format = {
year: "numeric", year: "numeric",
@ -54,36 +54,30 @@ const dateFormatter = v => {
return `${year}-${month}-${day}T${hour}:${minute}`; return `${year}-${month}-${day}T${hour}:${minute}`;
}; };
const RegistrationTokenFilter = props => ( const registrationTokenFilters = [<BooleanInput source="valid" alwaysOn />];
<Filter {...props}>
<BooleanInput source="valid" alwaysOn />
</Filter>
);
export const RegistrationTokenList = props => { export const RegistrationTokenList = props => (
return ( <List
<List {...props}
{...props} filters={registrationTokenFilters}
filters={<RegistrationTokenFilter />} filterDefaultValues={{ valid: true }}
filterDefaultValues={{ valid: true }} pagination={false}
pagination={false} perPage={500}
perPage={500} >
> <Datagrid rowClick="edit">
<Datagrid rowClick="edit"> <TextField source="token" sortable={false} />
<TextField source="token" sortable={false} /> <NumberField source="uses_allowed" sortable={false} />
<NumberField source="uses_allowed" sortable={false} /> <NumberField source="pending" sortable={false} />
<NumberField source="pending" sortable={false} /> <NumberField source="completed" sortable={false} />
<NumberField source="completed" sortable={false} /> <DateField
<DateField source="expiry_time"
source="expiry_time" showTime
showTime options={date_format}
options={date_format} sortable={false}
sortable={false} />
/> </Datagrid>
</Datagrid> </List>
</List> );
);
};
export const RegistrationTokenCreate = props => ( export const RegistrationTokenCreate = props => (
<Create redirect="list"> <Create redirect="list">
@ -117,24 +111,32 @@ export const RegistrationTokenCreate = props => (
</Create> </Create>
); );
export const RegistrationTokenEdit = props => { export const RegistrationTokenEdit = () => (
return ( <Edit>
<Edit> <SimpleForm>
<SimpleForm> <TextInput source="token" disabled />
<TextInput source="token" disabled /> <NumberInput source="pending" disabled />
<NumberInput source="pending" disabled /> <NumberInput source="completed" disabled />
<NumberInput source="completed" disabled /> <NumberInput
<NumberInput source="uses_allowed"
source="uses_allowed" validate={validateUsesAllowed}
validate={validateUsesAllowed} step={1}
step={1} />
/> <DateTimeInput
<DateTimeInput source="expiry_time"
source="expiry_time" parse={dateParser}
parse={dateParser} format={dateFormatter}
format={dateFormatter} />
/> </SimpleForm>
</SimpleForm> </Edit>
</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 React from "react";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import { import {
BooleanField, BooleanField,
BulkDeleteButton, BulkDeleteButton,
@ -23,6 +22,7 @@ import {
useUnselectAll, useUnselectAll,
} from "react-admin"; } from "react-admin";
import { useMutation } from "react-query"; import { useMutation } from "react-query";
import RoomDirectoryIcon from "@mui/icons-material/FolderShared";
import AvatarField from "./AvatarField"; import AvatarField from "./AvatarField";
const RoomDirectoryPagination = () => ( const RoomDirectoryPagination = () => (
@ -44,7 +44,7 @@ export const RoomDirectoryDeleteButton = () => {
smart_count: 1, smart_count: 1,
})} })}
resource="room_directory" resource="room_directory"
icon={<FolderSharedIcon />} icon={<RoomDirectoryIcon />}
/> />
); );
}; };
@ -56,7 +56,7 @@ export const RoomDirectoryBulkDeleteButton = () => (
confirmTitle="resources.room_directory.action.title" confirmTitle="resources.room_directory.action.title"
confirmContent="resources.room_directory.action.content" confirmContent="resources.room_directory.action.content"
resource="room_directory" resource="room_directory"
icon={<FolderSharedIcon />} icon={<RoomDirectoryIcon />}
/> />
); );
@ -91,7 +91,7 @@ export const RoomDirectoryBulkSaveButton = () => {
onClick={mutate} onClick={mutate}
disabled={isloading} disabled={isloading}
> >
<FolderSharedIcon /> <RoomDirectoryIcon />
</Button> </Button>
); );
}; };
@ -102,16 +102,16 @@ export const RoomDirectorySaveButton = () => {
const refresh = useRefresh(); const refresh = useRefresh();
const [create, { isloading }] = useCreate(); const [create, { isloading }] = useCreate();
const handleSend = values => { const handleSend = () => {
create( create(
"room_directory", "room_directory",
{ data: { id: record.id } }, { data: { id: record.id } },
{ {
onSuccess: () => { onSuccess: _data => {
notify("resources.room_directory.action.send_success"); notify("resources.room_directory.action.send_success");
refresh(); refresh();
}, },
onError: () => onError: _error =>
notify("resources.room_directory.action.send_failure", { notify("resources.room_directory.action.send_failure", {
type: "error", type: "error",
}), }),
@ -125,16 +125,12 @@ export const RoomDirectorySaveButton = () => {
onClick={handleSend} onClick={handleSend}
disabled={isloading} disabled={isloading}
> >
<FolderSharedIcon /> <RoomDirectoryIcon />
</Button> </Button>
); );
}; };
const RoomDirectoryBulkActionButtons = () => ( const RoomDirectoryBulkActionButtons = () => <RoomDirectoryBulkDeleteButton />;
<Fragment>
<RoomDirectoryBulkDeleteButton />
</Fragment>
);
const RoomDirectoryListActions = () => ( const RoomDirectoryListActions = () => (
<TopToolbar> <TopToolbar>
@ -198,3 +194,11 @@ export const RoomDirectoryList = () => (
</DatagridConfigurable> </DatagridConfigurable>
</List> </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 { import {
Button, Button,
SaveButton, SaveButton,
@ -91,7 +91,7 @@ export const ServerNoticeButton = () => {
}; };
return ( return (
<Fragment> <>
<Button <Button
label="resources.servernotices.send" label="resources.servernotices.send"
onClick={handleDialogOpen} onClick={handleDialogOpen}
@ -104,7 +104,7 @@ export const ServerNoticeButton = () => {
onClose={handleDialogClose} onClose={handleDialogClose}
onSubmit={handleSend} onSubmit={handleSend}
/> />
</Fragment> </>
); );
}; };
@ -137,7 +137,7 @@ export const ServerNoticeBulkButton = () => {
); );
return ( return (
<Fragment> <>
<Button <Button
label="resources.servernotices.send" label="resources.servernotices.send"
onClick={handleDialogOpen} onClick={handleDialogOpen}
@ -150,6 +150,6 @@ export const ServerNoticeBulkButton = () => {
onClose={handleDialogClose} onClose={handleDialogClose}
onSubmit={mutate} onSubmit={mutate}
/> />
</Fragment> </>
); );
}; };

View File

@ -3,7 +3,6 @@ import {
Button, Button,
Datagrid, Datagrid,
DateField, DateField,
Filter,
List, List,
Pagination, Pagination,
ReferenceField, ReferenceField,
@ -21,6 +20,7 @@ import {
useTranslate, useTranslate,
} from "react-admin"; } from "react-admin";
import AutorenewIcon from "@mui/icons-material/Autorenew"; import AutorenewIcon from "@mui/icons-material/Autorenew";
import DestinationsIcon from "@mui/icons-material/CloudQueue";
import FolderSharedIcon from "@mui/icons-material/FolderShared"; import FolderSharedIcon from "@mui/icons-material/FolderShared";
import ViewListIcon from "@mui/icons-material/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
@ -41,15 +41,9 @@ const destinationRowSx = (record, _index) => ({
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white", backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
}); });
const DestinationFilter = props => { const destinationFilters = [<SearchInput source="destination" alwaysOn />];
return (
<Filter {...props}>
<SearchInput source="destination" alwaysOn />
</Filter>
);
};
export const DestinationReconnectButton = props => { export const DestinationReconnectButton = () => {
const record = useRecordContext(); const record = useRecordContext();
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
@ -64,7 +58,7 @@ export const DestinationReconnectButton = props => {
handleReconnect( handleReconnect(
"destinations", "destinations",
{ id: record.id, previousData: record }, { id: record.id },
{ {
onSuccess: () => { onSuccess: () => {
notify("ra.notification.updated", { notify("ra.notification.updated", {
@ -90,7 +84,7 @@ export const DestinationReconnectButton = props => {
); );
}; };
const DestinationShowActions = props => ( const DestinationShowActions = () => (
<TopToolbar> <TopToolbar>
<DestinationReconnectButton /> <DestinationReconnectButton />
</TopToolbar> </TopToolbar>
@ -109,7 +103,7 @@ const DestinationTitle = () => {
export const DestinationList = () => { export const DestinationList = () => {
return ( return (
<List <List
filters={<DestinationFilter />} filters={destinationFilters}
pagination={<DestinationPagination />} pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }} sort={{ field: "destination", order: "ASC" }}
> >
@ -179,3 +173,12 @@ export const DestinationShow = () => {
</Show> </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 { import {
Button, Button,
useDelete, useDelete,
@ -25,10 +25,9 @@ export const DeviceRemoveButton = () => {
const handleDialogClose = () => setOpen(false); const handleDialogClose = () => setOpen(false);
const handleConfirm = () => { const handleConfirm = () => {
console.log(record);
removeDevice( removeDevice(
"devices", "devices",
{ id: record.id, previousData: record }, { id: record.id, meta: { user_id: record.user_id } },
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.devices.action.erase.success"); notify("resources.devices.action.erase.success");
@ -43,8 +42,9 @@ export const DeviceRemoveButton = () => {
}; };
return ( return (
<Fragment> <>
<Button <Button
{...props}
label="ra.action.remove" label="ra.action.remove"
onClick={handleClick} onClick={handleClick}
sx={{ sx={{
@ -72,6 +72,6 @@ export const DeviceRemoveButton = () => {
name: record.display_name ? record.display_name : record.id, 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 { import {
BooleanInput, BooleanInput,
Button, Button,
@ -99,10 +99,9 @@ export const DeleteMediaButton = () => {
const handleDialogClose = () => setOpen(false); const handleDialogClose = () => setOpen(false);
const handleSend = data => { const handleSend = data => {
console.log({ ...data });
deleteOne( deleteOne(
"delete_media", "delete_media",
{ previousData: { ...data } }, { id: data.id, meta: { ...data } },
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.delete_media.action.send_success"); notify("resources.delete_media.action.send_success");
@ -117,8 +116,9 @@ export const DeleteMediaButton = () => {
}; };
return ( return (
<Fragment> <>
<Button <Button
{...props}
label="resources.delete_media.action.send" label="resources.delete_media.action.send"
onClick={handleDialogOpen} onClick={handleDialogOpen}
disabled={isLoading} disabled={isLoading}
@ -140,7 +140,7 @@ export const DeleteMediaButton = () => {
onClose={handleDialogClose} onClose={handleDialogClose}
onSubmit={handleSend} onSubmit={handleSend}
/> />
</Fragment> </>
); );
}; };
@ -149,7 +149,7 @@ export const ProtectMediaButton = () => {
const translate = useTranslate(); const translate = useTranslate();
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
const [create, { loading }] = useCreate(); const [create, { isLoading }] = useCreate();
const [deleteOne] = useDelete(); const [deleteOne] = useDelete();
if (!record) return null; if (!record) return null;
@ -174,7 +174,7 @@ export const ProtectMediaButton = () => {
const handleUnprotect = () => { const handleUnprotect = () => {
deleteOne( deleteOne(
"protect_media", "protect_media",
{ id: record.id, previousData: record }, { id: record.id },
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.protect_media.action.send_success"); notify("resources.protect_media.action.send_success");
@ -193,7 +193,7 @@ export const ProtectMediaButton = () => {
Wrapping Tooltip with <div> Wrapping Tooltip with <div>
https://github.com/marmelab/react-admin/issues/4349#issuecomment-578594735 https://github.com/marmelab/react-admin/issues/4349#issuecomment-578594735
*/ */
<Fragment> <>
{record.quarantined_by && ( {record.quarantined_by && (
<Tooltip <Tooltip
title={translate("resources.protect_media.action.none", { title={translate("resources.protect_media.action.none", {
@ -219,7 +219,7 @@ export const ProtectMediaButton = () => {
arrow arrow
> >
<div> <div>
<Button onClick={handleUnprotect} disabled={loading}> <Button onClick={handleUnprotect} disabled={isLoading}>
<LockIcon /> <LockIcon />
</Button> </Button>
</div> </div>
@ -232,13 +232,13 @@ export const ProtectMediaButton = () => {
})} })}
> >
<div> <div>
<Button onClick={handleProtect} disabled={loading}> <Button onClick={handleProtect} disabled={isLoading}>
<LockOpenIcon /> <LockOpenIcon />
</Button> </Button>
</div> </div>
</Tooltip> </Tooltip>
)} )}
</Fragment> </>
); );
}; };
@ -247,7 +247,7 @@ export const QuarantineMediaButton = () => {
const translate = useTranslate(); const translate = useTranslate();
const refresh = useRefresh(); const refresh = useRefresh();
const notify = useNotify(); const notify = useNotify();
const [create, { loading }] = useCreate(); const [create, { isLoading }] = useCreate();
const [deleteOne] = useDelete(); const [deleteOne] = useDelete();
if (!record) return null; if (!record) return null;
@ -272,7 +272,7 @@ export const QuarantineMediaButton = () => {
const handleRemoveQuarantaine = () => { const handleRemoveQuarantaine = () => {
deleteOne( deleteOne(
"quarantine_media", "quarantine_media",
{ id: record.id, previousData: record }, { id: record.id },
{ {
onSuccess: () => { onSuccess: () => {
notify("resources.quarantine_media.action.send_success"); notify("resources.quarantine_media.action.send_success");
@ -287,7 +287,7 @@ export const QuarantineMediaButton = () => {
}; };
return ( return (
<Fragment> <>
{record.safe_from_quarantine && ( {record.safe_from_quarantine && (
<Tooltip <Tooltip
title={translate("resources.quarantine_media.action.none", { title={translate("resources.quarantine_media.action.none", {
@ -295,7 +295,7 @@ export const QuarantineMediaButton = () => {
})} })}
> >
<div> <div>
<Button disabled={true}> <Button {...props} disabled={true}>
<ClearIcon /> <ClearIcon />
</Button> </Button>
</div> </div>
@ -308,7 +308,11 @@ export const QuarantineMediaButton = () => {
})} })}
> >
<div> <div>
<Button onClick={handleRemoveQuarantaine} disabled={loading}> <Button
{...props}
onClick={handleRemoveQuarantaine}
disabled={isLoading}
>
<BlockIcon color="error" /> <BlockIcon color="error" />
</Button> </Button>
</div> </div>
@ -321,12 +325,12 @@ export const QuarantineMediaButton = () => {
})} })}
> >
<div> <div>
<Button onClick={handleQuarantaine} disabled={loading}> <Button onClick={handleQuarantaine} disabled={isLoading}>
<BlockIcon /> <BlockIcon />
</Button> </Button>
</div> </div>
</Tooltip> </Tooltip>
)} )}
</Fragment> </>
); );
}; };

View File

@ -1,4 +1,4 @@
import React, { Fragment } from "react"; import React from "react";
import { import {
BooleanField, BooleanField,
BulkDeleteButton, BulkDeleteButton,
@ -7,7 +7,6 @@ import {
DatagridConfigurable, DatagridConfigurable,
DeleteButton, DeleteButton,
ExportButton, ExportButton,
Filter,
FunctionField, FunctionField,
List, List,
NumberField, NumberField,
@ -35,6 +34,7 @@ import UserIcon from "@mui/icons-material/Group";
import ViewListIcon from "@mui/icons-material/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
import VisibilityIcon from "@mui/icons-material/Visibility"; import VisibilityIcon from "@mui/icons-material/Visibility";
import EventIcon from "@mui/icons-material/Event"; import EventIcon from "@mui/icons-material/Event";
import RoomIcon from "@mui/icons-material/ViewList";
import { import {
RoomDirectoryBulkDeleteButton, RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton, RoomDirectoryBulkSaveButton,
@ -274,7 +274,7 @@ export const RoomShow = () => {
}; };
const RoomBulkActionButtons = () => ( const RoomBulkActionButtons = () => (
<Fragment> <>
<RoomDirectoryBulkSaveButton /> <RoomDirectoryBulkSaveButton />
<RoomDirectoryBulkDeleteButton /> <RoomDirectoryBulkDeleteButton />
<BulkDeleteButton <BulkDeleteButton
@ -282,14 +282,10 @@ const RoomBulkActionButtons = () => (
confirmContent="resources.rooms.action.erase.content" confirmContent="resources.rooms.action.erase.content"
mutationMode="pessimistic" mutationMode="pessimistic"
/> />
</Fragment> </>
); );
const RoomFilter = props => ( const roomFilters = [<SearchInput source="search_term" alwaysOn />];
<Filter {...props}>
<SearchInput source="search_term" alwaysOn />
</Filter>
);
const RoomListActions = () => ( const RoomListActions = () => (
<TopToolbar> <TopToolbar>
@ -298,14 +294,15 @@ const RoomListActions = () => (
</TopToolbar> </TopToolbar>
); );
export const RoomList = () => { export const RoomList = props => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<List <List
{...props}
pagination={<RoomPagination />} pagination={<RoomPagination />}
sort={{ field: "name", order: "ASC" }} sort={{ field: "name", order: "ASC" }}
filters={<RoomFilter />} filters={roomFilters}
actions={<RoomListActions />} actions={<RoomListActions />}
> >
<DatagridConfigurable <DatagridConfigurable
@ -345,3 +342,12 @@ export const RoomList = () => {
</List> </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 { import {
Datagrid, Datagrid,
ExportButton, ExportButton,
Filter,
List, List,
NumberField, NumberField,
Pagination, Pagination,
@ -13,6 +12,7 @@ import {
TopToolbar, TopToolbar,
useListContext, useListContext,
} from "react-admin"; } from "react-admin";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import { DeleteMediaButton } from "./media"; import { DeleteMediaButton } from "./media";
const ListActions = props => { const ListActions = props => {
@ -45,32 +45,34 @@ const UserMediaStatsPagination = () => (
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
); );
const UserMediaStatsFilter = props => ( const userMediaStatsFilters = [<SearchInput source="search_term" alwaysOn />];
<Filter {...props}>
<SearchInput source="search_term" alwaysOn /> export const UserMediaStatsList = () => (
</Filter> <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 => { const resource = {
return ( name: "user_media_statistics",
<List icon: EqualizerIcon,
actions={<ListActions />} list: UserMediaStatsList,
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>
);
}; };
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 AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
import ContactMailIcon from "@mui/icons-material/ContactMail"; import ContactMailIcon from "@mui/icons-material/ContactMail";
import DevicesIcon from "@mui/icons-material/Devices"; 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 PermMediaIcon from "@mui/icons-material/PermMedia";
import PersonPinIcon from "@mui/icons-material/PersonPin"; import PersonPinIcon from "@mui/icons-material/PersonPin";
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent"; import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
import UserIcon from "@mui/icons-material/Group";
import ViewListIcon from "@mui/icons-material/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
import { import {
ArrayInput, ArrayInput,
@ -17,7 +18,6 @@ import {
Create, Create,
Edit, Edit,
List, List,
Filter,
Toolbar, Toolbar,
SimpleForm, SimpleForm,
SimpleFormIterator, SimpleFormIterator,
@ -125,60 +125,55 @@ const UserPagination = () => (
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} /> <Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
); );
const UserFilter = props => ( const userFilters = [
<Filter {...props}> <SearchInput source="name" alwaysOn />,
<SearchInput source="name" alwaysOn /> <BooleanInput source="guests" alwaysOn />,
<BooleanInput source="guests" alwaysOn /> <BooleanInput
<BooleanInput label="resources.users.fields.show_deactivated"
label="resources.users.fields.show_deactivated" source="deactivated"
source="deactivated" alwaysOn
alwaysOn />,
/> ];
</Filter>
);
const UserBulkActionButtons = props => ( const UserBulkActionButtons = () => (
<Fragment> <>
<ServerNoticeBulkButton {...props} /> <ServerNoticeBulkButton />
<BulkDeleteButton <BulkDeleteButton
{...props}
label="resources.users.action.erase" label="resources.users.action.erase"
confirmTitle="resources.users.helper.erase" confirmTitle="resources.users.helper.erase"
mutationMode="pessimistic" mutationMode="pessimistic"
/> />
</Fragment> </>
); );
export const UserList = props => { export const UserList = () => (
return ( <List
<List filters={userFilters}
filters={<UserFilter />} filterDefaultValues={{ guests: true, deactivated: false }}
filterDefaultValues={{ guests: true, deactivated: false }} sort={{ field: "name", order: "ASC" }}
sort={{ field: "name", order: "ASC" }} actions={<UserListActions maxResults={10000} />}
actions={<UserListActions maxResults={10000} />} pagination={<UserPagination />}
pagination={<UserPagination />} >
> <Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}>
<Datagrid rowClick="edit" bulkActionButtons={<UserBulkActionButtons />}> <AvatarField
<AvatarField source="avatar_src"
source="avatar_src" sx={{ height: "40px", width: "40px" }}
sx={{ height: "40px", width: "40px" }} sortBy="avatar_url"
sortBy="avatar_url" />
/> <TextField source="id" sortBy="name" />
<TextField source="id" sortBy="name" /> <TextField source="displayname" />
<TextField source="displayname" /> <BooleanField source="is_guest" />
<BooleanField source="is_guest" /> <BooleanField source="admin" />
<BooleanField source="admin" /> <BooleanField source="deactivated" />
<BooleanField source="deactivated" /> <DateField
<DateField source="creation_ts"
source="creation_ts" label="resources.users.fields.creation_ts_ms"
label="resources.users.fields.creation_ts_ms" showTime
showTime options={date_format}
options={date_format} />
/> </Datagrid>
</Datagrid> </List>
</List> );
);
};
// https://matrix.org/docs/spec/appendices#user-identifiers // https://matrix.org/docs/spec/appendices#user-identifiers
// here only local part of user_id // here only local part of user_id
@ -302,7 +297,7 @@ export const UserCreate = props => (
</Create> </Create>
); );
const UserTitle = props => { const UserTitle = () => {
const record = useRecordContext(); const record = useRecordContext();
const translate = useTranslate(); const translate = useTranslate();
return ( return (
@ -529,3 +524,13 @@ export const UserEdit = props => {
</Edit> </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 => ({ delete: params => ({
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent( endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
params.user_id params.meta.user_id
)}/devices/${params.id}`, )}/devices/${params.id}`,
}), }),
}, },
@ -184,9 +184,9 @@ const resourceMap = {
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/media/${localStorage.getItem( endpoint: `/_synapse/admin/v1/media/${localStorage.getItem(
"home_server" "home_server"
)}/delete?before_ts=${params.before_ts}&size_gt=${ )}/delete?before_ts=${params.meta.before_ts}&size_gt=${
params.size_gt params.meta.size_gt
}&keep_profiles=${params.keep_profiles}`, }&keep_profiles=${params.meta.keep_profiles}`,
method: "POST", method: "POST",
}), }),
}, },
@ -197,7 +197,7 @@ const resourceMap = {
method: "POST", method: "POST",
}), }),
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/media/unprotect/${params.media_id}`, endpoint: `/_synapse/admin/v1/media/unprotect/${params.id}`,
method: "POST", method: "POST",
}), }),
}, },
@ -212,7 +212,7 @@ const resourceMap = {
delete: params => ({ delete: params => ({
endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem( endpoint: `/_synapse/admin/v1/media/unquarantine/${localStorage.getItem(
"home_server" "home_server"
)}/${params.media_id}`, )}/${params.id}`,
method: "POST", method: "POST",
}), }),
}, },
@ -534,7 +534,7 @@ const dataProvider = {
const res = resourceMap[resource]; const res = resourceMap[resource];
if ("delete" in res) { if ("delete" in res) {
const del = res["delete"](params.previousData); const del = res["delete"](params);
const endpoint_url = homeserver + del.endpoint; const endpoint_url = homeserver + del.endpoint;
return jsonClient(endpoint_url, { return jsonClient(endpoint_url, {
method: "method" in del ? del.method : "DELETE", method: "method" in del ? del.method : "DELETE",
@ -546,7 +546,7 @@ const dataProvider = {
const endpoint_url = homeserver + res.path; const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`, { return jsonClient(`${endpoint_url}/${params.id}`, {
method: "DELETE", method: "DELETE",
body: JSON.stringify(params.previousData, filterNullValues), body: JSON.stringify(params, filterNullValues),
}).then(({ json }) => ({ }).then(({ json }) => ({
data: json, data: json,
})); }));