Merge branch 'master' into french-translation

This commit is contained in:
Michael Albert 2023-01-24 21:37:24 +01:00 committed by GitHub
commit ccf53288ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 5175 additions and 5670 deletions

20
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
# Major updates for react-admin have breaking changes
- dependency-name: "react-admin"
update-types: ["version-update:semver-major"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Run tests

View File

@ -17,13 +17,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -43,7 +43,7 @@ jobs:
esac
echo "::set-output name=tag::$tag"
- name: Build and Push Tag
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: .
push: true

View File

@ -10,17 +10,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1
- uses: actions/setup-node@v2
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14"
node-version: "16"
- name: Install and Build 🔧
run: |
yarn install
yarn build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@4.1.5
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
folder: build

View File

@ -13,10 +13,10 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14"
node-version: "16"
- run: yarn install
- run: yarn build
- run: |
@ -24,7 +24,7 @@ jobs:
mkdir -p dist
cp -r build synapse-admin-$version
tar chvzf dist/synapse-admin-$version.tar.gz synapse-admin-$version
- uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
with:
files: dist/*.tar.gz
env:

View File

@ -6,6 +6,6 @@
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"bracketSameLine": false,
"arrowParens": "avoid"
}

View File

@ -1,5 +1,6 @@
dist: focal
language: node_js
node_js:
- lts/*
- 17
cache: yarn

View File

@ -1,5 +1,9 @@
[![Build Status](https://travis-ci.org/Awesome-Technologies/synapse-admin.svg?branch=master)](https://travis-ci.org/Awesome-Technologies/synapse-admin)
[![GitHub license](https://img.shields.io/github/license/Awesome-Technologies/synapse-admin)](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
[![Build Status](https://api.travis-ci.com/Awesome-Technologies/synapse-admin.svg?branch=master)](https://app.travis-ci.com/github/Awesome-Technologies/synapse-admin)
[![build-test](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/build-test.yml)
[![gh-pages](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/edge_ghpage.yml/badge.svg)](https://awesome-technologies.github.io/synapse-admin/)
[![docker-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/docker-release.yml/badge.svg)](https://hub.docker.com/r/awesometechnologies/synapse-admin)
[![github-release](https://github.com/Awesome-Technologies/synapse-admin/actions/workflows/github-release.yml/badge.svg)](https://github.com/Awesome-Technologies/synapse-admin/releases)
# Synapse admin ui
@ -9,7 +13,7 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
### Supported Synapse
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.42.0 for all functions to work as expected!
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.52.0 for all functions to work as expected!
You get your server version with the request `/_synapse/admin/v1/server_version`.
See also [Synapse version API](https://matrix-org.github.io/synapse/develop/admin_api/version_api.html).
@ -82,8 +86,8 @@ or by editing it in the [.env](.env) file. See also the
context: https://github.com/Awesome-Technologies/synapse-admin.git
# args:
# - NODE_OPTIONS="--max_old_space_size=1024"
# # see #266
# - PUBLIC_URL="/synapse-admin"
# # see #266, PUBLIC_URL must be without surrounding quotation marks
# - PUBLIC_URL=/synapse-admin
# - REACT_APP_SERVER="https://matrix.example.com"
ports:
- "8080:80"

View File

@ -12,10 +12,15 @@ services:
# replace the context definition with this:
# context: https://github.com/Awesome-Technologies/synapse-admin.git
# args:
# if you're building on an architecture other than amd64, make sure
# to define a maximum ram for node. otherwise the build will fail.
# args:
# - NODE_OPTIONS="--max_old_space_size=1024"
# default is /
# - PUBLIC_URL=/synapse-admin
# You can use a fixed homeserver, so that the user can no longer
# define it himself
# - REACT_APP_SERVER="https://matrix.example.com"
ports:
- "8080:80"
restart: unless-stopped

View File

@ -10,17 +10,22 @@
"url": "https://github.com/Awesome-Technologies/synapse-admin"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.1.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.8",
"eslint": "^7.25.0",
"@testing-library/user-event": "^14.4.3",
"eslint": "^8.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.2.0",
"ra-test": "^3.15.0"
},
"dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.3.1",
"@mui/material": "^5.4.0",
"papaparse": "^5.2.0",
"prop-types": "^15.7.2",
"ra-language-chinese": "^2.0.10",
@ -29,7 +34,7 @@
"react": "^17.0.0",
"react-admin": "^3.19.7",
"react-dom": "^17.0.2",
"react-scripts": "^4.0.0"
"react-scripts": "^5.0.1"
},
"scripts": {
"start": "REACT_APP_VERSION=$(git describe --tags) react-scripts start",

View File

@ -7,13 +7,15 @@ import { UserList, UserCreate, UserEdit } from "./components/users";
import { RoomList, RoomShow } from "./components/rooms";
import { ReportList, ReportShow } from "./components/EventReports";
import LoginPage from "./components/LoginPage";
import UserIcon from "@material-ui/icons/Group";
import ConfirmationNumberIcon from "@material-ui/icons/ConfirmationNumber";
import EqualizerIcon from "@material-ui/icons/Equalizer";
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 "@material-ui/icons/ViewList";
import ReportIcon from "@material-ui/icons/Warning";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
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,
@ -74,6 +76,12 @@ const App = () => (
list={RoomDirectoryList}
icon={FolderSharedIcon}
/>
<Resource
name="destinations"
list={DestinationList}
show={DestinationShow}
icon={CloudQueueIcon}
/>
<Resource
name="registration_tokens"
list={RegistrationTokenList}
@ -90,6 +98,7 @@ const App = () => (
<Resource name="servernotices" />
<Resource name="forward_extremities" />
<Resource name="room_state" />
<Resource name="destination_rooms" />
</Admin>
);

View File

@ -12,8 +12,8 @@ import {
TextField,
useTranslate,
} from "react-admin";
import PageviewIcon from "@material-ui/icons/Pageview";
import ViewListIcon from "@material-ui/icons/ViewList";
import PageviewIcon from "@mui/icons-material/Pageview";
import ViewListIcon from "@mui/icons-material/ViewList";
const date_format = {
year: "numeric",

View File

@ -6,19 +6,19 @@ import {
Title,
} from "react-admin";
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
import GetAppIcon from "@material-ui/icons/GetApp";
import GetAppIcon from "@mui/icons-material/GetApp";
import {
Button,
Card,
CardActions,
CardContent,
CardHeader,
FormControlLabel,
Checkbox,
Container,
FormControlLabel,
NativeSelect,
} from "@material-ui/core";
} from "@mui/material";
import { useTranslate } from "ra-core";
import Container from "@material-ui/core/Container/Container";
import { generateRandomUser } from "./users";
const LOGGING = true;

View File

@ -21,9 +21,9 @@ import {
MenuItem,
Select,
TextField,
} from "@material-ui/core";
} from "@mui/material";
import { makeStyles } from "@material-ui/core/styles";
import LockIcon from "@material-ui/icons/Lock";
import LockIcon from "@mui/icons-material/Lock";
const useStyles = makeStyles(theme => ({
main: {
@ -169,7 +169,7 @@ const LoginPage = ({ theme }) => {
: typeof error === "undefined" || !error.message
? "ra.auth.sign_in_error"
: error.message,
"warning"
{ type: "warning" }
);
});
};
@ -269,7 +269,7 @@ const LoginPage = ({ theme }) => {
autoFocus
name="username"
component={renderInput}
label={translate("ra.auth.username")}
label="ra.auth.username"
disabled={loading || !supportPassAuth}
onBlur={handleUsernameChange}
resettable
@ -280,7 +280,7 @@ const LoginPage = ({ theme }) => {
<PasswordInput
name="password"
component={renderInput}
label={translate("ra.auth.password")}
label="ra.auth.password"
type="password"
disabled={loading || !supportPassAuth}
resettable
@ -291,7 +291,7 @@ const LoginPage = ({ theme }) => {
<TextInput
name="base_url"
component={renderInput}
label={translate("synapseadmin.auth.base_url")}
label="synapseadmin.auth.base_url"
disabled={cfg_base_url || loading}
resettable
fullWidth

View File

@ -1,10 +1,10 @@
// in src/Menu.js
import * as React from "react";
import { useSelector } from "react-redux";
import { useMediaQuery } from "@material-ui/core";
import { useMediaQuery } from "@mui/material";
import { MenuItemLink, getResources } from "react-admin";
import DefaultIcon from "@material-ui/icons/ViewList";
import LabelIcon from "@material-ui/icons/Label";
import DefaultIcon from "@mui/icons-material/ViewList";
import LabelIcon from "@mui/icons-material/Label";
const Menu = ({ onMenuClick, logout }) => {
const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs"));

View File

@ -1,8 +1,7 @@
import React, { Fragment } from "react";
import Avatar from "@material-ui/core/Avatar";
import { Chip } from "@material-ui/core";
import { Avatar, Chip } from "@mui/material";
import { connect } from "react-redux";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import FolderSharedIcon from "@mui/icons-material/FolderShared";
import { makeStyles } from "@material-ui/core/styles";
import {
BooleanField,
@ -19,6 +18,7 @@ import {
useMutation,
useNotify,
useTranslate,
useRecordContext,
useRefresh,
useUnselectAll,
} from "react-admin";
@ -87,7 +87,9 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
refresh();
},
onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"),
notify("resources.room_directory.action.send_failure", {
type: "error",
}),
}
);
};
@ -103,7 +105,8 @@ export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
);
};
export const RoomDirectorySaveButton = ({ record }) => {
export const RoomDirectorySaveButton = props => {
const record = useRecordContext();
const notify = useNotify();
const refresh = useRefresh();
const [create, { loading }] = useCreate("room_directory");
@ -119,7 +122,9 @@ export const RoomDirectorySaveButton = ({ record }) => {
refresh();
},
onFailure: error =>
notify("resources.room_directory.action.send_failure", "error"),
notify("resources.room_directory.action.send_failure", {
type: "error",
}),
}
);
};
@ -177,7 +182,6 @@ export const FilterableRoomDirectoryList = ({
...props
}) => {
const classes = useStyles();
const translate = useTranslate();
const filter = roomDirectoryFilters;
const roomIdFilter = filter && filter.room_id ? true : false;
const topicFilter = filter && filter.topic ? true : false;
@ -196,48 +200,48 @@ export const FilterableRoomDirectoryList = ({
source="avatar_src"
sortable={false}
className={classes.small}
label={translate("resources.rooms.fields.avatar")}
label="resources.rooms.fields.avatar"
/>
<TextField
source="name"
sortable={false}
label={translate("resources.rooms.fields.name")}
label="resources.rooms.fields.name"
/>
{roomIdFilter && (
<TextField
source="room_id"
sortable={false}
label={translate("resources.rooms.fields.room_id")}
label="resources.rooms.fields.room_id"
/>
)}
{canonicalAliasFilter && (
<TextField
source="canonical_alias"
sortable={false}
label={translate("resources.rooms.fields.canonical_alias")}
label="resources.rooms.fields.canonical_alias"
/>
)}
{topicFilter && (
<TextField
source="topic"
sortable={false}
label={translate("resources.rooms.fields.topic")}
label="resources.rooms.fields.topic"
/>
)}
<NumberField
source="num_joined_members"
sortable={false}
label={translate("resources.rooms.fields.joined_members")}
label="resources.rooms.fields.joined_members"
/>
<BooleanField
source="world_readable"
sortable={false}
label={translate("resources.room_directory.fields.world_readable")}
label="resources.room_directory.fields.world_readable"
/>
<BooleanField
source="guest_can_join"
sortable={false}
label={translate("resources.room_directory.fields.guest_can_join")}
label="resources.room_directory.fields.guest_can_join"
/>
</Datagrid>
</List>

View File

@ -9,15 +9,18 @@ import {
useCreate,
useMutation,
useNotify,
useRecordContext,
useTranslate,
useUnselectAll,
} from "react-admin";
import MessageIcon from "@material-ui/icons/Message";
import IconCancel from "@material-ui/icons/Cancel";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import MessageIcon from "@mui/icons-material/Message";
import IconCancel from "@mui/icons-material/Cancel";
import {
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material";
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
const translate = useTranslate();
@ -64,7 +67,8 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
);
};
export const ServerNoticeButton = ({ record }) => {
export const ServerNoticeButton = props => {
const record = useRecordContext();
const [open, setOpen] = useState(false);
const notify = useNotify();
const [create, { loading }] = useCreate("servernotices");
@ -81,7 +85,9 @@ export const ServerNoticeButton = ({ record }) => {
handleDialogClose();
},
onFailure: () =>
notify("resources.servernotices.action.send_failure", "error"),
notify("resources.servernotices.action.send_failure", {
type: "error",
}),
}
);
};
@ -127,7 +133,9 @@ export const ServerNoticeBulkButton = ({ selectedIds }) => {
handleDialogClose();
},
onFailure: error =>
notify("resources.servernotices.action.send_failure", "error"),
notify("resources.servernotices.action.send_failure", {
type: "error",
}),
}
);
};

View File

@ -0,0 +1,185 @@
import React from "react";
import {
Button,
Datagrid,
DateField,
Filter,
List,
Pagination,
ReferenceField,
ReferenceManyField,
SearchInput,
Show,
Tab,
TabbedShowLayout,
TextField,
TopToolbar,
useRecordContext,
useDelete,
useNotify,
useRefresh,
useTranslate,
} from "react-admin";
import AutorenewIcon from "@material-ui/icons/Autorenew";
import FolderSharedIcon from "@material-ui/icons/FolderShared";
import ViewListIcon from "@material-ui/icons/ViewList";
const DestinationPagination = props => (
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
);
const date_format = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
};
const destinationRowStyle = (record, index) => ({
backgroundColor: record.retry_last_ts > 0 ? "#ffcccc" : "white",
});
const DestinationFilter = ({ ...props }) => {
return (
<Filter {...props}>
<SearchInput source="destination" alwaysOn />
</Filter>
);
};
export const DestinationReconnectButton = props => {
const record = useRecordContext();
const refresh = useRefresh();
const notify = useNotify();
const [handleReconnect, { isLoading }] = useDelete("destinations");
// Reconnect is not required if no error has occurred. (`failure_ts`)
if (!record || !record.failure_ts) return null;
const handleClick = e => {
// Prevents redirection to the detail page when clicking in the list
e.stopPropagation();
handleReconnect(
{ payload: { id: record.id } },
{
onSuccess: () => {
notify("ra.notification.updated", {
messageArgs: { smart_count: 1 },
});
refresh();
},
onFailure: () => {
notify("ra.message.error", { type: "error" });
},
}
);
};
return (
<Button
label="resources.destinations.action.reconnect"
onClick={handleClick}
disabled={isLoading}
>
<AutorenewIcon />
</Button>
);
};
const DestinationShowActions = props => (
<TopToolbar>
<DestinationReconnectButton />
</TopToolbar>
);
const DestinationTitle = props => {
const record = useRecordContext();
const translate = useTranslate();
return (
<span>
{translate("resources.destinations.name", 1)} {record.destination}
</span>
);
};
export const DestinationList = props => {
return (
<List
{...props}
filters={<DestinationFilter />}
pagination={<DestinationPagination />}
sort={{ field: "destination", order: "ASC" }}
bulkActionButtons={false}
>
<Datagrid
rowStyle={destinationRowStyle}
rowClick={(id, basePath, record) => `${basePath}/${id}/show/rooms`}
>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
<DestinationReconnectButton />
</Datagrid>
</List>
);
};
export const DestinationShow = props => {
const translate = useTranslate();
return (
<Show
actions={<DestinationShowActions />}
title={<DestinationTitle />}
{...props}
>
<TabbedShowLayout>
<Tab label="status" icon={<ViewListIcon />}>
<TextField source="destination" />
<DateField source="failure_ts" showTime options={date_format} />
<DateField source="retry_last_ts" showTime options={date_format} />
<TextField source="retry_interval" />
<TextField source="last_successful_stream_ordering" />
</Tab>
<Tab
label={translate("resources.rooms.name", { smart_count: 2 })}
icon={<FolderSharedIcon />}
path="rooms"
>
<ReferenceManyField
reference="destination_rooms"
target="destination"
addLabel={false}
pagination={<DestinationPagination />}
perPage={50}
>
<Datagrid
style={{ width: "100%" }}
rowClick={(id, basePath, record) => `/rooms/${id}/show`}
>
<TextField
source="room_id"
label="resources.rooms.fields.room_id"
/>
<TextField source="stream_ordering" sortable={false} />
<ReferenceField
label="resources.rooms.fields.name"
source="id"
reference="rooms"
sortable={false}
link=""
>
<TextField source="name" sortable={false} />
</ReferenceField>
</Datagrid>
</ReferenceManyField>
</Tab>
</TabbedShowLayout>
</Show>
);
};

View File

@ -1,14 +1,15 @@
import React, { Fragment, useState } from "react";
import {
Button,
useMutation,
useDelete,
useNotify,
Confirm,
useRecordContext,
useRefresh,
} from "react-admin";
import ActionDelete from "@material-ui/icons/Delete";
import ActionDelete from "@mui/icons-material/Delete";
import { makeStyles } from "@material-ui/core/styles";
import { alpha } from "@material-ui/core/styles/colorManipulator";
import { alpha } from "@mui/material/styles";
import classnames from "classnames";
const useStyles = makeStyles(
@ -28,13 +29,13 @@ const useStyles = makeStyles(
);
export const DeviceRemoveButton = props => {
const { record } = props;
const record = useRecordContext();
const classes = useStyles(props);
const [open, setOpen] = useState(false);
const refresh = useRefresh();
const notify = useNotify();
const [removeDevice, { loading }] = useMutation();
const [removeDevice, { isLoading }] = useDelete("devices");
if (!record) return null;
@ -43,21 +44,15 @@ export const DeviceRemoveButton = props => {
const handleConfirm = () => {
removeDevice(
{
type: "delete",
resource: "devices",
payload: {
id: record.id,
user_id: record.user_id,
},
},
{ payload: { id: record.id, user_id: record.user_id } },
{
onSuccess: () => {
notify("resources.devices.action.erase.success");
refresh();
},
onFailure: () =>
notify("resources.devices.action.erase.failure", "error"),
onFailure: () => {
notify("resources.devices.action.erase.failure", { type: "error" });
},
}
);
setOpen(false);
@ -74,7 +69,7 @@ export const DeviceRemoveButton = props => {
</Button>
<Confirm
isOpen={open}
loading={loading}
loading={isLoading}
onConfirm={handleConfirm}
onClose={handleDialogClose}
title="resources.devices.action.erase.title"

View File

@ -1,8 +1,7 @@
import React, { Fragment, useState } from "react";
import classnames from "classnames";
import { alpha } from "@material-ui/core/styles/colorManipulator";
import { alpha } from "@mui/material/styles";
import { makeStyles } from "@material-ui/core/styles";
import { Tooltip } from "@material-ui/core";
import {
BooleanInput,
Button,
@ -14,19 +13,23 @@ import {
useCreate,
useDelete,
useNotify,
useRecordContext,
useRefresh,
useTranslate,
} from "react-admin";
import BlockIcon from "@material-ui/icons/Block";
import ClearIcon from "@material-ui/icons/Clear";
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import IconCancel from "@material-ui/icons/Cancel";
import LockIcon from "@material-ui/icons/Lock";
import LockOpenIcon from "@material-ui/icons/LockOpen";
import BlockIcon from "@mui/icons-material/Block";
import ClearIcon from "@mui/icons-material/Clear";
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
import {
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
Tooltip,
} from "@mui/material";
import IconCancel from "@mui/icons-material/Cancel";
import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
const useStyles = makeStyles(
theme => ({
@ -127,7 +130,9 @@ export const DeleteMediaButton = props => {
handleDialogClose();
},
onFailure: () =>
notify("resources.delete_media.action.send_failure", "error"),
notify("resources.delete_media.action.send_failure", {
type: "error",
}),
}
);
};
@ -152,7 +157,7 @@ export const DeleteMediaButton = props => {
};
export const ProtectMediaButton = props => {
const { record } = props;
const record = useRecordContext();
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
@ -170,7 +175,9 @@ export const ProtectMediaButton = props => {
refresh();
},
onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"),
notify("resources.protect_media.action.send_failure", {
type: "error",
}),
}
);
};
@ -184,7 +191,9 @@ export const ProtectMediaButton = props => {
refresh();
},
onFailure: () =>
notify("resources.protect_media.action.send_failure", "error"),
notify("resources.protect_media.action.send_failure", {
type: "error",
}),
}
);
};
@ -244,7 +253,7 @@ export const ProtectMediaButton = props => {
};
export const QuarantineMediaButton = props => {
const { record } = props;
const record = useRecordContext();
const translate = useTranslate();
const refresh = useRefresh();
const notify = useNotify();
@ -262,7 +271,9 @@ export const QuarantineMediaButton = props => {
refresh();
},
onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"),
notify("resources.quarantine_media.action.send_failure", {
type: "error",
}),
}
);
};
@ -276,7 +287,9 @@ export const QuarantineMediaButton = props => {
refresh();
},
onFailure: () =>
notify("resources.quarantine_media.action.send_failure", "error"),
notify("resources.quarantine_media.action.send_failure", {
type: "error",
}),
}
);
};

View File

@ -25,15 +25,15 @@ import {
import get from "lodash/get";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import { Tooltip, Typography, Chip } from "@material-ui/core";
import FastForwardIcon from "@material-ui/icons/FastForward";
import HttpsIcon from "@material-ui/icons/Https";
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
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 EventIcon from "@material-ui/icons/Event";
import { Tooltip, Typography, Chip } from "@mui/material";
import FastForwardIcon from "@mui/icons-material/FastForward";
import HttpsIcon from "@mui/icons-material/Https";
import NoEncryptionIcon from "@mui/icons-material/NoEncryption";
import PageviewIcon from "@mui/icons-material/Pageview";
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 {
RoomDirectoryBulkDeleteButton,
RoomDirectoryBulkSaveButton,
@ -87,7 +87,8 @@ const EncryptionField = ({ source, record = {}, emptyText }) => {
);
};
const RoomTitle = ({ record }) => {
const RoomTitle = props => {
const record = useRecordContext();
const translate = useTranslate();
var name = "";
if (record) {
@ -354,7 +355,7 @@ const RoomFilter = ({ ...props }) => {
const RoomNameField = props => {
const { source } = props;
const record = useRecordContext(props);
const record = useRecordContext();
return (
<span>{record[source] || record["canonical_alias"] || record["id"]}</span>
);

View File

@ -1,14 +1,14 @@
import React, { cloneElement, Fragment } from "react";
import Avatar from "@material-ui/core/Avatar";
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
import ContactMailIcon from "@material-ui/icons/ContactMail";
import DevicesIcon from "@material-ui/icons/Devices";
import GetAppIcon from "@material-ui/icons/GetApp";
import NotificationsIcon from "@material-ui/icons/Notifications";
import PermMediaIcon from "@material-ui/icons/PermMedia";
import PersonPinIcon from "@material-ui/icons/PersonPin";
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
import ViewListIcon from "@material-ui/icons/ViewList";
import Avatar from "@mui/material/Avatar";
import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
import ContactMailIcon from "@mui/icons-material/ContactMail";
import DevicesIcon from "@mui/icons-material/Devices";
import GetAppIcon from "@mui/icons-material/GetApp";
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 ViewListIcon from "@mui/icons-material/ViewList";
import {
ArrayInput,
ArrayField,
@ -39,6 +39,7 @@ import {
maxLength,
regex,
required,
useRecordContext,
useTranslate,
Pagination,
CreateButton,
@ -71,6 +72,16 @@ const useStyles = makeStyles({
},
});
const choices_medium = [
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
];
const choices_type = [
{ id: "bot", name: "bot" },
{ id: "support", name: "support" },
];
const date_format = {
year: "numeric",
month: "2-digit",
@ -249,20 +260,31 @@ export function generateRandomUser() {
};
}
const UserEditToolbar = props => {
const translate = useTranslate();
return (
const UserEditToolbar = props => (
<Toolbar {...props}>
<SaveButton submitOnEnter={true} disabled={props.pristine} />
</Toolbar>
);
const UserEditActions = ({ data }) => {
const translate = useTranslate();
var userStatus = "";
if (data) {
userStatus = data.deactivated;
}
return (
<TopToolbar>
{!userStatus && <ServerNoticeButton record={data} />}
<DeleteButton
record={data}
label="resources.users.action.erase"
confirmTitle={translate("resources.users.helper.erase", {
smart_count: 1,
})}
mutationMode="pessimistic"
/>
<ServerNoticeButton />
</Toolbar>
</TopToolbar>
);
};
@ -276,15 +298,19 @@ export const UserCreate = props => (
autoComplete="new-password"
validate={maxLength(512)}
/>
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" />
<ArrayInput source="threepids">
<SimpleFormIterator disableReordering>
<SelectInput
source="medium"
choices={[
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
choices={choices_medium}
validate={required()}
/>
<TextInput source="address" validate={validateAddress} />
@ -304,7 +330,8 @@ export const UserCreate = props => (
</Create>
);
const UserTitle = ({ record }) => {
const UserTitle = props => {
const record = useRecordContext();
const translate = useTranslate();
return (
<span>
@ -315,11 +342,12 @@ const UserTitle = ({ record }) => {
</span>
);
};
export const UserEdit = props => {
const classes = useStyles();
const translate = useTranslate();
return (
<Edit {...props} title={<UserTitle />}>
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
<TabbedForm toolbar={<UserEditToolbar />}>
<FormTab
label={translate("resources.users.name", { smart_count: 1 })}
@ -332,7 +360,18 @@ export const UserEdit = props => {
/>
<TextInput source="id" disabled />
<TextInput source="displayname" />
<PasswordInput source="password" autoComplete="new-password" />
<PasswordInput
source="password"
autoComplete="new-password"
helperText="resources.users.helper.password"
/>
<SelectInput
source="user_type"
choices={choices_type}
translateChoice={false}
allowEmpty={true}
resettable
/>
<BooleanInput source="admin" />
<BooleanInput
source="deactivated"
@ -349,13 +388,7 @@ export const UserEdit = props => {
>
<ArrayInput source="threepids">
<SimpleFormIterator disableReordering>
<SelectInput
source="medium"
choices={[
{ id: "email", name: "resources.users.email" },
{ id: "msisdn", name: "resources.users.msisdn" },
]}
/>
<SelectInput source="medium" choices={choices_medium} />
<TextInput source="address" />
</SimpleFormIterator>
</ArrayInput>

View File

@ -121,8 +121,11 @@ const de = {
creation_ts_ms: "Zeitpunkt der Erstellung",
consent_version: "Zugestimmte Geschäftsbedingungen",
auth_provider: "Provider",
user_type: "Benutzertyp",
},
helper: {
password:
"Durch die Änderung des Passworts wird der Benutzer von allen Sitzungen abgemeldet.",
deactivate:
"Sie müssen ein Passwort angeben, um ein Konto wieder zu aktivieren.",
erase: "DSGVO konformes Löschen der Benutzerdaten",
@ -352,6 +355,18 @@ const de = {
send_failure: "Beim Entfernen ist ein Fehler aufgetreten.",
},
},
destinations: {
name: "Föderation",
fields: {
destination: "Ziel",
failure_ts: "Fehlerzeitpunkt",
retry_last_ts: "Letzter Wiederholungsversuch",
retry_interval: "Wiederholungsintervall",
last_successful_stream_ordering: "letzte erfogreicher Stream",
stream_ordering: "Stream",
},
action: { reconnect: "Neu verbinden" },
},
registration_tokens: {
name: "Registrierungstoken",
fields: {

View File

@ -120,8 +120,10 @@ const en = {
creation_ts_ms: "Creation timestamp",
consent_version: "Consent version",
auth_provider: "Provider",
user_type: "User type",
},
helper: {
password: "Changing password will log user out of all sessions.",
deactivate: "You must provide a password to re-activate an account.",
erase: "Mark the user as GDPR-erased",
},
@ -350,6 +352,18 @@ const en = {
send_failure: "An error has occurred.",
},
},
destinations: {
name: "Federation",
fields: {
destination: "Destination",
failure_ts: "Failure timestamp",
retry_last_ts: "Last retry timestamp",
retry_interval: "Retry interval",
last_successful_stream_ordering: "Last successful stream",
stream_ordering: "Stream",
},
action: { reconnect: "Reconnect" },
},
},
registration_tokens: {
name: "Registration tokens",

View File

@ -41,14 +41,16 @@ const resourceMap = {
data: "users",
total: json => json.total,
create: data => ({
endpoint: `/_synapse/admin/v2/users/@${data.id}:${localStorage.getItem(
"home_server"
)}`,
endpoint: `/_synapse/admin/v2/users/@${encodeURIComponent(
data.id
)}:${localStorage.getItem("home_server")}`,
body: data,
method: "PUT",
}),
delete: params => ({
endpoint: `/_synapse/admin/v1/deactivate/${params.id}`,
endpoint: `/_synapse/admin/v1/deactivate/${encodeURIComponent(
params.id
)}`,
body: { erase: true },
method: "POST",
}),
@ -69,7 +71,7 @@ const resourceMap = {
return json.total_rooms;
},
delete: params => ({
endpoint: `/_synapse/admin/v1/rooms/${params.id}`,
endpoint: `/_synapse/admin/v2/rooms/${params.id}`,
body: { block: false },
}),
},
@ -92,10 +94,12 @@ const resourceMap = {
return json.total;
},
reference: id => ({
endpoint: `/_synapse/admin/v2/users/${id}/devices`,
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(id)}/devices`,
}),
delete: params => ({
endpoint: `/_synapse/admin/v2/users/${params.user_id}/devices/${params.id}`,
endpoint: `/_synapse/admin/v2/users/${encodeURIComponent(
params.user_id
)}/devices/${params.id}`,
}),
},
connections: {
@ -137,7 +141,7 @@ const resourceMap = {
id: p.pushkey,
}),
reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/pushers`,
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/pushers`,
}),
data: "pushers",
total: json => {
@ -149,7 +153,9 @@ const resourceMap = {
id: jr,
}),
reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/joined_rooms`,
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(
id
)}/joined_rooms`,
}),
data: "joined_rooms",
total: json => {
@ -162,7 +168,7 @@ const resourceMap = {
id: um.media_id,
}),
reference: id => ({
endpoint: `/_synapse/admin/v1/users/${id}/media`,
endpoint: `/_synapse/admin/v1/users/${encodeURIComponent(id)}/media`,
}),
data: "media",
total: json => {
@ -275,6 +281,34 @@ const resourceMap = {
method: "PUT",
}),
},
destinations: {
path: "/_synapse/admin/v1/federation/destinations",
map: dst => ({
...dst,
id: dst.destination,
}),
data: "destinations",
total: json => {
return json.total;
},
delete: params => ({
endpoint: `/_synapse/admin/v1/federation/destinations/${params.id}/reset_connection`,
method: "POST",
}),
},
destination_rooms: {
map: dstroom => ({
...dstroom,
id: dstroom.room_id,
}),
reference: id => ({
endpoint: `/_synapse/admin/v1/federation/destinations/${id}/rooms`,
}),
data: "rooms",
total: json => {
return json.total;
},
},
registration_tokens: {
path: "/_synapse/admin/v1/registration_tokens",
map: rt => ({
@ -298,7 +332,8 @@ const resourceMap = {
function filterNullValues(key, value) {
// Filtering out null properties
if (value === null) {
// to reset user_type from user, it must be null
if (value === null && key !== "user_type") {
return undefined;
}
return value;
@ -315,8 +350,15 @@ function getSearchOrder(order) {
const dataProvider = {
getList: (resource, params) => {
console.log("getList " + resource);
const { user_id, name, guests, deactivated, search_term, valid } =
params.filter;
const {
user_id,
name,
guests,
deactivated,
search_term,
destination,
valid,
} = params.filter;
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const from = (page - 1) * perPage;
@ -326,6 +368,7 @@ const dataProvider = {
user_id: user_id,
search_term: search_term,
name: name,
destination: destination,
guests: guests,
deactivated: deactivated,
valid: valid,
@ -354,9 +397,11 @@ const dataProvider = {
const res = resourceMap[resource];
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.id}`).then(({ json }) => ({
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.id)}`).then(
({ json }) => ({
data: res.map(json),
}));
})
);
},
getMany: (resource, params) => {
@ -368,7 +413,9 @@ const dataProvider = {
const endpoint_url = homeserver + res.path;
return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`))
params.ids.map(id =>
jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`)
)
).then(responses => ({
data: responses.map(({ json }) => res.map(json)),
total: responses.length,
@ -409,7 +456,7 @@ const dataProvider = {
const res = resourceMap[resource];
const endpoint_url = homeserver + res.path;
return jsonClient(`${endpoint_url}/${params.data.id}`, {
return jsonClient(`${endpoint_url}/${encodeURIComponent(params.data.id)}`, {
method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
}).then(({ json }) => ({
@ -426,10 +473,13 @@ const dataProvider = {
const endpoint_url = homeserver + res.path;
return Promise.all(
params.ids.map(id => jsonClient(`${endpoint_url}/${id}`), {
params.ids.map(
id => jsonClient(`${endpoint_url}/${encodeURIComponent(id)}`),
{
method: "PUT",
body: JSON.stringify(params.data, filterNullValues),
})
}
)
).then(responses => ({
data: responses.map(({ json }) => json),
}));

10143
yarn.lock

File diff suppressed because it is too large Load Diff