Merge branch 'master' into user_erased_details
This commit is contained in:
commit
ae5727b74c
6
.github/workflows/build-test.yml
vendored
6
.github/workflows/build-test.yml
vendored
@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: "18"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn --frozen-lockfile
|
run: yarn --frozen-lockfile
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
10
.github/workflows/docker-release.yml
vendored
10
.github/workflows/docker-release.yml
vendored
@ -17,13 +17,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
echo "::set-output name=tag::$tag"
|
echo "::set-output name=tag::$tag"
|
||||||
- name: Build and Push Tag
|
- name: Build and Push Tag
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
8
.github/workflows/edge_ghpage.yml
vendored
8
.github/workflows/edge_ghpage.yml
vendored
@ -10,17 +10,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout 🛎️
|
- name: Checkout 🛎️
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "18"
|
||||||
- name: Install and Build 🔧
|
- name: Install and Build 🔧
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
- name: Deploy 🚀
|
- name: Deploy 🚀
|
||||||
uses: JamesIves/github-pages-deploy-action@v4.4.1
|
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||||
with:
|
with:
|
||||||
branch: gh-pages
|
branch: gh-pages
|
||||||
folder: build
|
folder: build
|
||||||
|
6
.github/workflows/github-release.yml
vendored
6
.github/workflows/github-release.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "18"
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: |
|
- run: |
|
||||||
|
51
.github/workflows/test-docker-image.yml
vendored
Normal file
51
.github/workflows/test-docker-image.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
name: Test docker image creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Sequence of patterns matched against refs/heads
|
||||||
|
# prettier-ignore
|
||||||
|
branches:
|
||||||
|
# Push events on branch fix_docker_cd
|
||||||
|
- fix_docker_cd
|
||||||
|
# Sequence of patterns matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]+' # Push events to 0.X.X tag
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Calculate docker image tag
|
||||||
|
id: set-tag
|
||||||
|
run: |
|
||||||
|
case "${GITHUB_REF}" in
|
||||||
|
refs/heads/master|refs/heads/main)
|
||||||
|
tag=latest
|
||||||
|
;;
|
||||||
|
refs/tags/*)
|
||||||
|
tag=${GITHUB_REF#refs/tags/}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
tag=${GITHUB_SHA}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "::set-output name=tag::$tag"
|
||||||
|
- name: Build and Push Tag
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: "awesometechnologies/synapse-admin:${{ steps.set-tag.outputs.tag }}"
|
||||||
|
platforms: linux/amd64,linux/arm64
|
@ -1,6 +1,6 @@
|
|||||||
dist: focal
|
dist: focal
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 17
|
- 18
|
||||||
|
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
# Builder
|
# Builder
|
||||||
FROM node:lts as builder
|
FROM node:lts as builder
|
||||||
|
|
||||||
ARG PUBLIC_URL=/
|
|
||||||
ARG REACT_APP_SERVER
|
ARG REACT_APP_SERVER
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
RUN yarn --network-timeout=100000 install
|
RUN yarn --network-timeout=300000 install
|
||||||
RUN PUBLIC_URL=$PUBLIC_URL REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
RUN REACT_APP_SERVER=$REACT_APP_SERVER yarn build
|
||||||
|
|
||||||
|
|
||||||
# App
|
# App
|
||||||
|
@ -13,10 +13,10 @@ This project is built using [react-admin](https://marmelab.com/react-admin/).
|
|||||||
|
|
||||||
### Supported Synapse
|
### Supported Synapse
|
||||||
|
|
||||||
It needs at least [Synapse](https://github.com/matrix-org/synapse) v1.71.0 for all functions to work as expected!
|
It needs at least [Synapse](https://github.com/element-hq/synapse) v1.71.0 for all functions to work as expected!
|
||||||
|
|
||||||
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
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).
|
See also [Synapse version API](https://element-hq.github.io/synapse/latest/admin_api/version_api.html).
|
||||||
|
|
||||||
After entering the URL on the login page of synapse-admin the server version appears below the input field.
|
After entering the URL on the login page of synapse-admin the server version appears below the input field.
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ You need access to the following endpoints:
|
|||||||
- `/_matrix`
|
- `/_matrix`
|
||||||
- `/_synapse/admin`
|
- `/_synapse/admin`
|
||||||
|
|
||||||
See also [Synapse administration endpoints](https://matrix-org.github.io/synapse/develop/reverse_proxy.html#synapse-administration-endpoints)
|
See also [Synapse administration endpoints](https://element-hq.github.io/synapse/latest/reverse_proxy.html#synapse-administration-endpoints)
|
||||||
|
|
||||||
### Use without install
|
### Use without install
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ services:
|
|||||||
# if you're building on an architecture other than amd64, make sure
|
# if you're building on an architecture other than amd64, make sure
|
||||||
# to define a maximum ram for node. otherwise the build will fail.
|
# to define a maximum ram for node. otherwise the build will fail.
|
||||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||||
# default is /
|
# default is .
|
||||||
# - PUBLIC_URL=/synapse-admin
|
# - PUBLIC_URL=/synapse-admin
|
||||||
# You can use a fixed homeserver, so that the user can no longer
|
# You can use a fixed homeserver, so that the user can no longer
|
||||||
# define it himself
|
# define it himself
|
||||||
|
22
package.json
22
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "synapse-admin",
|
"name": "synapse-admin",
|
||||||
"version": "0.8.5",
|
"version": "0.9.0",
|
||||||
"description": "Admin GUI for the Matrix.org server Synapse",
|
"description": "Admin GUI for the Matrix.org server Synapse",
|
||||||
"author": "Awesome Technologies Innovationslabor GmbH",
|
"author": "Awesome Technologies Innovationslabor GmbH",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -11,23 +11,27 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/react": "^12.1.5",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"eslint": "^8.32.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"prettier": "^2.2.0",
|
"prettier": "^2.2.0"
|
||||||
"ra-test": "^3.15.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"papaparse": "^5.2.0",
|
"@mui/icons-material": "^5.14.19",
|
||||||
"prop-types": "^15.7.2",
|
"@mui/material": "^5.14.8",
|
||||||
|
"@mui/styles": "5.14.10",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
"ra-language-chinese": "^2.0.10",
|
"ra-language-chinese": "^2.0.10",
|
||||||
|
"ra-language-french": "^4.16.2",
|
||||||
"ra-language-german": "^3.13.4",
|
"ra-language-german": "^3.13.4",
|
||||||
|
"ra-language-italian": "^3.13.1",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-admin": "^3.19.7",
|
"react-admin": "^4.16.9",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "^5.0.1"
|
"react-scripts": "^5.0.1"
|
||||||
},
|
},
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
User-agent: *
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
86
src/App.js
86
src/App.js
@ -1,37 +1,35 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Admin, Resource, resolveBrowserLocale } from "react-admin";
|
import {
|
||||||
|
Admin,
|
||||||
|
CustomRoutes,
|
||||||
|
Resource,
|
||||||
|
resolveBrowserLocale,
|
||||||
|
} from "react-admin";
|
||||||
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 "@material-ui/icons/ConfirmationNumber";
|
|
||||||
import CloudQueueIcon from "@material-ui/icons/CloudQueue";
|
|
||||||
import EqualizerIcon from "@material-ui/icons/Equalizer";
|
|
||||||
import UserIcon from "@material-ui/icons/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 { 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";
|
||||||
|
import frenchMessages from "./i18n/fr";
|
||||||
import chineseMessages from "./i18n/zh";
|
import chineseMessages from "./i18n/zh";
|
||||||
|
import italianMessages from "./i18n/it";
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
de: germanMessages,
|
de: germanMessages,
|
||||||
en: englishMessages,
|
en: englishMessages,
|
||||||
|
fr: frenchMessages,
|
||||||
|
it: italianMessages,
|
||||||
zh: chineseMessages,
|
zh: chineseMessages,
|
||||||
};
|
};
|
||||||
const i18nProvider = polyglotI18nProvider(
|
const i18nProvider = polyglotI18nProvider(
|
||||||
@ -46,47 +44,17 @@ const App = () => (
|
|||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
i18nProvider={i18nProvider}
|
i18nProvider={i18nProvider}
|
||||||
customRoutes={[
|
|
||||||
<Route key="userImport" path="/import_users" component={ImportFeature} />,
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Resource
|
<CustomRoutes>
|
||||||
name="users"
|
<Route path="/import_users" element={<ImportFeature />} />
|
||||||
list={UserList}
|
</CustomRoutes>
|
||||||
create={UserCreate}
|
<Resource {...users} />
|
||||||
edit={UserEdit}
|
<Resource {...rooms} />
|
||||||
icon={UserIcon}
|
<Resource {...userMediaStats} />
|
||||||
/>
|
<Resource {...reports} />
|
||||||
<Resource name="rooms" list={RoomList} show={RoomShow} icon={RoomIcon} />
|
<Resource {...roomDirectory} />
|
||||||
<Resource
|
<Resource {...destinations} />
|
||||||
name="user_media_statistics"
|
<Resource {...registrationToken} />
|
||||||
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" />
|
||||||
|
12
src/components/AvatarField.js
Normal file
12
src/components/AvatarField.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
import get from "lodash/get";
|
||||||
|
import { Avatar } from "@mui/material";
|
||||||
|
import { useRecordContext } from "react-admin";
|
||||||
|
|
||||||
|
const AvatarField = ({ source, ...rest }) => {
|
||||||
|
const record = useRecordContext(rest);
|
||||||
|
const src = get(record, source)?.toString();
|
||||||
|
return <Avatar src={src} {...rest} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarField;
|
@ -12,8 +12,9 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import PageviewIcon from "@material-ui/icons/Pageview";
|
import PageviewIcon from "@mui/icons-material/Pageview";
|
||||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
import ReportIcon from "@mui/icons-material/Warning";
|
||||||
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
|
|
||||||
const date_format = {
|
const date_format = {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
@ -24,8 +25,8 @@ const date_format = {
|
|||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReportPagination = props => (
|
const ReportPagination = () => (
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ReportShow = props => {
|
export const ReportShow = props => {
|
||||||
@ -79,6 +80,7 @@ export const ReportShow = props => {
|
|||||||
<ReferenceField source="sender" reference="users">
|
<ReferenceField source="sender" reference="users">
|
||||||
<TextField source="id" />
|
<TextField source="id" />
|
||||||
</ReferenceField>
|
</ReferenceField>
|
||||||
|
<TextField source="sender" label="Sender (raw user ID)" />
|
||||||
<TextField source="event_id" />
|
<TextField source="event_id" />
|
||||||
<TextField source="event_json.origin" />
|
<TextField source="event_json.origin" />
|
||||||
<TextField source="event_json.type" />
|
<TextField source="event_json.type" />
|
||||||
@ -89,7 +91,7 @@ export const ReportShow = props => {
|
|||||||
<TextField source="event_json.content.algorithm" />
|
<TextField source="event_json.content.algorithm" />
|
||||||
<TextField
|
<TextField
|
||||||
source="event_json.content.device_id"
|
source="event_json.content.device_id"
|
||||||
label="resources.users.fields.device_id"
|
label="resources.devices.fields.device_id"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabbedShowLayout>
|
</TabbedShowLayout>
|
||||||
@ -97,26 +99,32 @@ export const ReportShow = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReportList = ({ ...props }) => {
|
export const ReportList = props => (
|
||||||
return (
|
<List
|
||||||
<List
|
{...props}
|
||||||
{...props}
|
pagination={<ReportPagination />}
|
||||||
pagination={<ReportPagination />}
|
sort={{ field: "received_ts", order: "DESC" }}
|
||||||
sort={{ field: "received_ts", order: "DESC" }}
|
>
|
||||||
bulkActionButtons={false}
|
<Datagrid rowClick="show" bulkActionButtons={false}>
|
||||||
>
|
<TextField source="id" sortable={false} />
|
||||||
<Datagrid rowClick="show">
|
<DateField
|
||||||
<TextField source="id" sortable={false} />
|
source="received_ts"
|
||||||
<DateField
|
showTime
|
||||||
source="received_ts"
|
options={date_format}
|
||||||
showTime
|
sortable={true}
|
||||||
options={date_format}
|
/>
|
||||||
sortable={true}
|
<TextField sortable={false} source="user_id" />
|
||||||
/>
|
<TextField sortable={false} source="name" />
|
||||||
<TextField sortable={false} source="user_id" />
|
<TextField sortable={false} source="score" />
|
||||||
<TextField sortable={false} source="name" />
|
</Datagrid>
|
||||||
<TextField sortable={false} source="score" />
|
</List>
|
||||||
</Datagrid>
|
);
|
||||||
</List>
|
|
||||||
);
|
const resource = {
|
||||||
|
name: "reports",
|
||||||
|
icon: ReportIcon,
|
||||||
|
list: ReportList,
|
||||||
|
show: ReportShow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default resource;
|
||||||
|
@ -1,41 +1,22 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import { useDataProvider, useNotify, Title } from "react-admin";
|
||||||
Button as ReactAdminButton,
|
|
||||||
useDataProvider,
|
|
||||||
useNotify,
|
|
||||||
Title,
|
|
||||||
} from "react-admin";
|
|
||||||
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
|
import { parse as parseCsv, unparse as unparseCsv } from "papaparse";
|
||||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardActions,
|
CardActions,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
FormControlLabel,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Container,
|
||||||
|
FormControlLabel,
|
||||||
NativeSelect,
|
NativeSelect,
|
||||||
} from "@material-ui/core";
|
} from "@mui/material";
|
||||||
import { useTranslate } from "ra-core";
|
import { useTranslate } from "ra-core";
|
||||||
import Container from "@material-ui/core/Container/Container";
|
|
||||||
import { generateRandomUser } from "./users";
|
import { generateRandomUser } from "./users";
|
||||||
|
|
||||||
const LOGGING = true;
|
const LOGGING = true;
|
||||||
|
|
||||||
export const ImportButton = ({ label, variant = "text" }) => {
|
|
||||||
return (
|
|
||||||
<ReactAdminButton
|
|
||||||
color="primary"
|
|
||||||
component="span"
|
|
||||||
variant={variant}
|
|
||||||
label={label}
|
|
||||||
>
|
|
||||||
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
|
||||||
</ReactAdminButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const expectedFields = ["id", "displayname"].sort();
|
const expectedFields = ["id", "displayname"].sort();
|
||||||
const optionalFields = [
|
const optionalFields = [
|
||||||
"user_type",
|
"user_type",
|
||||||
@ -51,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);
|
||||||
@ -210,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;
|
||||||
@ -326,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") {
|
||||||
@ -351,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 " +
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
fetchUtils,
|
fetchUtils,
|
||||||
|
Form,
|
||||||
FormDataConsumer,
|
FormDataConsumer,
|
||||||
Notification,
|
Notification,
|
||||||
|
required,
|
||||||
useLogin,
|
useLogin,
|
||||||
useNotify,
|
useNotify,
|
||||||
useLocale,
|
useLocaleState,
|
||||||
useSetLocale,
|
|
||||||
useTranslate,
|
useTranslate,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Form, useForm } from "react-final-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardActions,
|
CardActions,
|
||||||
@ -21,66 +23,64 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@material-ui/core";
|
Typography,
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
} from "@mui/material";
|
||||||
import LockIcon from "@material-ui/icons/Lock";
|
import { styled } from "@mui/material/styles";
|
||||||
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const FormBox = styled(Box)(({ theme }) => ({
|
||||||
main: {
|
display: "flex",
|
||||||
display: "flex",
|
flexDirection: "column",
|
||||||
flexDirection: "column",
|
minHeight: "calc(100vh - 1em)",
|
||||||
minHeight: "calc(100vh - 1em)",
|
alignItems: "center",
|
||||||
alignItems: "center",
|
justifyContent: "flex-start",
|
||||||
justifyContent: "flex-start",
|
background: "url(./images/floating-cogs.svg)",
|
||||||
background: "url(./images/floating-cogs.svg)",
|
backgroundColor: "#f9f9f9",
|
||||||
backgroundColor: "#f9f9f9",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundSize: "cover",
|
||||||
backgroundSize: "cover",
|
|
||||||
},
|
[`& .card`]: {
|
||||||
card: {
|
|
||||||
minWidth: "30em",
|
minWidth: "30em",
|
||||||
marginTop: "6em",
|
marginTop: "6em",
|
||||||
marginBottom: "6em",
|
marginBottom: "6em",
|
||||||
},
|
},
|
||||||
avatar: {
|
[`& .avatar`]: {
|
||||||
margin: "1em",
|
margin: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
icon: {
|
[`& .icon`]: {
|
||||||
backgroundColor: theme.palette.secondary.main,
|
backgroundColor: theme.palette.grey[500],
|
||||||
},
|
},
|
||||||
hint: {
|
[`& .hint`]: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[600],
|
||||||
},
|
},
|
||||||
form: {
|
[`& .form`]: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
input: {
|
[`& .input`]: {
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
},
|
},
|
||||||
actions: {
|
[`& .actions`]: {
|
||||||
padding: "0 1em 1em 1em",
|
padding: "0 1em 1em 1em",
|
||||||
},
|
},
|
||||||
serverVersion: {
|
[`& .serverVersion`]: {
|
||||||
color: "#9e9e9e",
|
color: theme.palette.grey[500],
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
marginBottom: "1em",
|
marginBottom: "1em",
|
||||||
marginLeft: "0.5em",
|
marginLeft: "0.5em",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const LoginPage = ({ theme }) => {
|
const LoginPage = () => {
|
||||||
const classes = useStyles({ theme });
|
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
const [supportPassAuth, setSupportPassAuth] = useState(true);
|
||||||
var locale = useLocale();
|
const [locale, setLocale] = useLocaleState();
|
||||||
const setLocale = useSetLocale();
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const base_url = localStorage.getItem("base_url");
|
const base_url = localStorage.getItem("base_url");
|
||||||
const cfg_base_url = process.env.REACT_APP_SERVER;
|
const cfg_base_url = process.env.REACT_APP_SERVER;
|
||||||
@ -135,28 +135,16 @@ const LoginPage = ({ theme }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const validate = values => {
|
const validateBaseUrl = value => {
|
||||||
const errors = {};
|
if (!value.match(/^(http|https):\/\//)) {
|
||||||
if (!values.username) {
|
return translate("synapseadmin.auth.protocol_error");
|
||||||
errors.username = translate("ra.validation.required");
|
} else if (
|
||||||
}
|
!value.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/)
|
||||||
if (!values.password) {
|
) {
|
||||||
errors.password = translate("ra.validation.required");
|
return translate("synapseadmin.auth.url_error");
|
||||||
}
|
|
||||||
if (!values.base_url) {
|
|
||||||
errors.base_url = translate("ra.validation.required");
|
|
||||||
} else {
|
} else {
|
||||||
if (!values.base_url.match(/^(http|https):\/\//)) {
|
return undefined;
|
||||||
errors.base_url = translate("synapseadmin.auth.protocol_error");
|
|
||||||
} else if (
|
|
||||||
!values.base_url.match(
|
|
||||||
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?[^?&\s]*$/
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
errors.base_url = translate("synapseadmin.auth.url_error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return errors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = auth => {
|
const handleSubmit = auth => {
|
||||||
@ -191,7 +179,7 @@ const LoginPage = ({ theme }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UserData = ({ formData }) => {
|
const UserData = ({ formData }) => {
|
||||||
const form = useForm();
|
const form = useFormContext();
|
||||||
const [serverVersion, setServerVersion] = useState("");
|
const [serverVersion, setServerVersion] = useState("");
|
||||||
|
|
||||||
const handleUsernameChange = _ => {
|
const handleUsernameChange = _ => {
|
||||||
@ -204,11 +192,11 @@ const LoginPage = ({ theme }) => {
|
|||||||
fetchUtils
|
fetchUtils
|
||||||
.fetchJson(wellKnownUrl, { method: "GET" })
|
.fetchJson(wellKnownUrl, { method: "GET" })
|
||||||
.then(({ json }) => {
|
.then(({ json }) => {
|
||||||
form.change("base_url", json["m.homeserver"].base_url);
|
form.setValue("base_url", json["m.homeserver"].base_url);
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
// if there is no .well-known entry, try the home server name
|
// if there is no .well-known entry, try the home server name
|
||||||
form.change("base_url", `https://${home_server}`);
|
form.setValue("base_url", `https://${home_server}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -217,7 +205,9 @@ const LoginPage = ({ theme }) => {
|
|||||||
_ => {
|
_ => {
|
||||||
if (
|
if (
|
||||||
!formData.base_url ||
|
!formData.base_url ||
|
||||||
!formData.base_url.match(/^(http|https):\/\/[a-zA-Z0-9\-.]+$/)
|
!formData.base_url.match(
|
||||||
|
/^(http|https):\/\/[a-zA-Z0-9\-.]+(:\d{1,5})?$/
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
|
const versionUrl = `${formData.base_url}/_synapse/admin/v1/server_version`;
|
||||||
@ -263,111 +253,113 @@ const LoginPage = ({ theme }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<div className={classes.input}>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
name="username"
|
name="username"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("ra.auth.username")}
|
label="ra.auth.username"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
onBlur={handleUsernameChange}
|
onBlur={handleUsernameChange}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className={classes.input}>
|
<Box>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
name="password"
|
name="password"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("ra.auth.password")}
|
label="ra.auth.password"
|
||||||
type="password"
|
type="password"
|
||||||
disabled={loading || !supportPassAuth}
|
disabled={loading || !supportPassAuth}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
|
validate={required()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className={classes.input}>
|
<Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
name="base_url"
|
name="base_url"
|
||||||
component={renderInput}
|
component={renderInput}
|
||||||
label={translate("synapseadmin.auth.base_url")}
|
label="synapseadmin.auth.base_url"
|
||||||
disabled={cfg_base_url || loading}
|
disabled={cfg_base_url || loading}
|
||||||
resettable
|
resettable
|
||||||
fullWidth
|
fullWidth
|
||||||
|
className="input"
|
||||||
|
validate={[required(), validateBaseUrl]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className={classes.serverVersion}>{serverVersion}</div>
|
<Typography className="serverVersion">{serverVersion}</Typography>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
initialValues={{ base_url: cfg_base_url || base_url }}
|
defaultValues={{ base_url: cfg_base_url || base_url }}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
validate={validate}
|
mode="onTouched"
|
||||||
render={({ handleSubmit }) => (
|
>
|
||||||
<form onSubmit={handleSubmit} noValidate>
|
<FormBox>
|
||||||
<div className={classes.main}>
|
<Card className="card">
|
||||||
<Card className={classes.card}>
|
<Box className="avatar">
|
||||||
<div className={classes.avatar}>
|
{loading ? (
|
||||||
<Avatar className={classes.icon}>
|
<CircularProgress size={25} thickness={2} />
|
||||||
<LockIcon />
|
) : (
|
||||||
</Avatar>
|
<Avatar className="icon">
|
||||||
</div>
|
<LockIcon />
|
||||||
<div className={classes.hint}>
|
</Avatar>
|
||||||
{translate("synapseadmin.auth.welcome")}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
<div className={classes.form}>
|
<Box className="hint">{translate("synapseadmin.auth.welcome")}</Box>
|
||||||
<div className={classes.input}>
|
<Box className="form">
|
||||||
<Select
|
<Select
|
||||||
value={locale}
|
value={locale}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setLocale(e.target.value);
|
setLocale(e.target.value);
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
className="input"
|
||||||
<MenuItem value="de">Deutsch</MenuItem>
|
>
|
||||||
<MenuItem value="en">English</MenuItem>
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
<MenuItem value="zh">简体中文</MenuItem>
|
<MenuItem value="en">English</MenuItem>
|
||||||
</Select>
|
<MenuItem value="fr">Français</MenuItem>
|
||||||
</div>
|
<MenuItem value="it">Italiano</MenuItem>
|
||||||
<FormDataConsumer>
|
<MenuItem value="zh">简体中文</MenuItem>
|
||||||
{formDataProps => <UserData {...formDataProps} />}
|
</Select>
|
||||||
</FormDataConsumer>
|
<FormDataConsumer>
|
||||||
</div>
|
{formDataProps => <UserData {...formDataProps} />}
|
||||||
<CardActions className={classes.actions}>
|
</FormDataConsumer>
|
||||||
<Button
|
<CardActions className="actions">
|
||||||
variant="contained"
|
<Button
|
||||||
type="submit"
|
variant="contained"
|
||||||
color="primary"
|
type="submit"
|
||||||
disabled={loading || !supportPassAuth}
|
color="primary"
|
||||||
className={classes.button}
|
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
|
variant="contained"
|
||||||
variant="contained"
|
color="secondary"
|
||||||
color="secondary"
|
onClick={handleSSO}
|
||||||
onClick={handleSSO}
|
disabled={loading || ssoBaseUrl === ""}
|
||||||
disabled={loading || ssoBaseUrl === ""}
|
fullWidth
|
||||||
className={classes.button}
|
>
|
||||||
fullWidth
|
{translate("synapseadmin.auth.sso_sign_in")}
|
||||||
>
|
</Button>
|
||||||
{loading && <CircularProgress size={25} thickness={2} />}
|
</CardActions>
|
||||||
{translate("synapseadmin.auth.sso_sign_in")}
|
</Box>
|
||||||
</Button>
|
</Card>
|
||||||
</CardActions>
|
</FormBox>
|
||||||
</Card>
|
<Notification />
|
||||||
<Notification />
|
</Form>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { TestContext } from "ra-test";
|
import { AdminContext } from "react-admin";
|
||||||
import LoginPage from "./LoginPage";
|
import LoginPage from "./LoginPage";
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
describe("LoginForm", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
render(
|
render(
|
||||||
<TestContext>
|
<AdminContext>
|
||||||
<LoginPage />
|
<LoginPage />
|
||||||
</TestContext>
|
</AdminContext>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
// in src/Menu.js
|
|
||||||
import * as React from "react";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useMediaQuery } from "@material-ui/core";
|
|
||||||
import { MenuItemLink, getResources } from "react-admin";
|
|
||||||
import DefaultIcon from "@material-ui/icons/ViewList";
|
|
||||||
import LabelIcon from "@material-ui/icons/Label";
|
|
||||||
|
|
||||||
const Menu = ({ onMenuClick, logout }) => {
|
|
||||||
const isXSmall = useMediaQuery(theme => theme.breakpoints.down("xs"));
|
|
||||||
const open = useSelector(state => state.admin.ui.sidebarOpen);
|
|
||||||
const resources = useSelector(getResources);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{resources.map(resource => (
|
|
||||||
<MenuItemLink
|
|
||||||
key={resource.name}
|
|
||||||
to={`/${resource.name}`}
|
|
||||||
primaryText={
|
|
||||||
(resource.options && resource.options.label) || resource.name
|
|
||||||
}
|
|
||||||
leftIcon={resource.icon ? <resource.icon /> : <DefaultIcon />}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
sidebarIsOpen={open}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<MenuItemLink
|
|
||||||
to="/custom-route"
|
|
||||||
primaryText="Miscellaneous"
|
|
||||||
leftIcon={<LabelIcon />}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
sidebarIsOpen={open}
|
|
||||||
/>
|
|
||||||
{isXSmall && logout}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Menu;
|
|
@ -6,18 +6,19 @@ import {
|
|||||||
DateField,
|
DateField,
|
||||||
DateTimeInput,
|
DateTimeInput,
|
||||||
Edit,
|
Edit,
|
||||||
Filter,
|
|
||||||
List,
|
List,
|
||||||
maxValue,
|
maxValue,
|
||||||
number,
|
number,
|
||||||
NumberField,
|
NumberField,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
regex,
|
regex,
|
||||||
|
SaveButton,
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
TextInput,
|
TextInput,
|
||||||
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",
|
||||||
@ -53,40 +54,41 @@ 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 />
|
export const RegistrationTokenList = props => (
|
||||||
</Filter>
|
<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 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 RegistrationTokenCreate = props => (
|
export const RegistrationTokenCreate = props => (
|
||||||
<Create {...props}>
|
<Create {...props} redirect="list">
|
||||||
<SimpleForm redirect="list" toolbar={<Toolbar alwaysEnableSaveButton />}>
|
<SimpleForm
|
||||||
|
toolbar={
|
||||||
|
<Toolbar>
|
||||||
|
{/* It is possible to create tokens per default without input. */}
|
||||||
|
<SaveButton alwaysEnable />
|
||||||
|
</Toolbar>
|
||||||
|
}
|
||||||
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
source="token"
|
source="token"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -109,24 +111,32 @@ export const RegistrationTokenCreate = props => (
|
|||||||
</Create>
|
</Create>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RegistrationTokenEdit = props => {
|
export const RegistrationTokenEdit = props => (
|
||||||
return (
|
<Edit {...props}>
|
||||||
<Edit {...props}>
|
<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;
|
||||||
|
@ -1,41 +1,35 @@
|
|||||||
import React, { Fragment } from "react";
|
import React from "react";
|
||||||
import Avatar from "@material-ui/core/Avatar";
|
|
||||||
import { Chip } from "@material-ui/core";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import FolderSharedIcon from "@material-ui/icons/FolderShared";
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
import {
|
import {
|
||||||
BooleanField,
|
BooleanField,
|
||||||
BulkDeleteButton,
|
BulkDeleteButton,
|
||||||
Button,
|
Button,
|
||||||
Datagrid,
|
DatagridConfigurable,
|
||||||
|
ExportButton,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
Filter,
|
|
||||||
List,
|
List,
|
||||||
NumberField,
|
NumberField,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
SelectColumnsButton,
|
||||||
TextField,
|
TextField,
|
||||||
|
TopToolbar,
|
||||||
useCreate,
|
useCreate,
|
||||||
useMutation,
|
useDataProvider,
|
||||||
|
useListContext,
|
||||||
useNotify,
|
useNotify,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
useRefresh,
|
useRefresh,
|
||||||
useUnselectAll,
|
useUnselectAll,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
import { useMutation } from "react-query";
|
||||||
|
import RoomDirectoryIcon from "@mui/icons-material/FolderShared";
|
||||||
|
import AvatarField from "./AvatarField";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const RoomDirectoryPagination = () => (
|
||||||
small: {
|
<Pagination rowsPerPageOptions={[100, 500, 1000, 2000]} />
|
||||||
height: "40px",
|
|
||||||
width: "40px",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const RoomDirectoryPagination = props => (
|
|
||||||
<Pagination {...props} rowsPerPageOptions={[100, 500, 1000, 2000]} />
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RoomDirectoryDeleteButton = props => {
|
export const RoomDirectoryUnpublishButton = props => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -51,12 +45,12 @@ export const RoomDirectoryDeleteButton = props => {
|
|||||||
smart_count: 1,
|
smart_count: 1,
|
||||||
})}
|
})}
|
||||||
resource="room_directory"
|
resource="room_directory"
|
||||||
icon={<FolderSharedIcon />}
|
icon={<RoomDirectoryIcon />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoomDirectoryBulkDeleteButton = props => (
|
export const RoomDirectoryBulkUnpublishButton = props => (
|
||||||
<BulkDeleteButton
|
<BulkDeleteButton
|
||||||
{...props}
|
{...props}
|
||||||
label="resources.room_directory.action.erase"
|
label="resources.room_directory.action.erase"
|
||||||
@ -64,65 +58,63 @@ export const RoomDirectoryBulkDeleteButton = props => (
|
|||||||
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 />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RoomDirectoryBulkSaveButton = ({ selectedIds }) => {
|
export const RoomDirectoryBulkPublishButton = props => {
|
||||||
|
const { selectedIds } = useListContext();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const unselectAll = useUnselectAll();
|
const unselectAllRooms = useUnselectAll("rooms");
|
||||||
const [createMany, { loading }] = useMutation();
|
const dataProvider = useDataProvider();
|
||||||
|
const { mutate, isLoading } = useMutation(
|
||||||
const handleSend = values => {
|
() =>
|
||||||
createMany(
|
dataProvider.createMany("room_directory", {
|
||||||
{
|
ids: selectedIds,
|
||||||
type: "createMany",
|
data: {},
|
||||||
resource: "room_directory",
|
}),
|
||||||
payload: { ids: selectedIds, data: {} },
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
notify("resources.room_directory.action.send_success");
|
||||||
|
unselectAllRooms();
|
||||||
|
refresh();
|
||||||
},
|
},
|
||||||
{
|
onError: () =>
|
||||||
onSuccess: ({ data }) => {
|
notify("resources.room_directory.action.send_failure", {
|
||||||
notify("resources.room_directory.action.send_success");
|
type: "error",
|
||||||
unselectAll("rooms");
|
}),
|
||||||
refresh();
|
}
|
||||||
},
|
);
|
||||||
onFailure: error =>
|
|
||||||
notify("resources.room_directory.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
{...props}
|
||||||
label="resources.room_directory.action.create"
|
label="resources.room_directory.action.create"
|
||||||
onClick={handleSend}
|
onClick={mutate}
|
||||||
disabled={loading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<FolderSharedIcon />
|
<RoomDirectoryIcon />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoomDirectorySaveButton = props => {
|
export const RoomDirectoryPublishButton = props => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const [create, { loading }] = useCreate("room_directory");
|
const [create, { isLoading }] = useCreate();
|
||||||
|
|
||||||
const handleSend = values => {
|
const handleSend = () => {
|
||||||
create(
|
create(
|
||||||
|
"room_directory",
|
||||||
|
{ data: { id: record.id } },
|
||||||
{
|
{
|
||||||
payload: { data: { id: record.id } },
|
onSuccess: () => {
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: ({ data }) => {
|
|
||||||
notify("resources.room_directory.action.send_success");
|
notify("resources.room_directory.action.send_success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: error =>
|
onError: () =>
|
||||||
notify("resources.room_directory.action.send_failure", {
|
notify("resources.room_directory.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -132,131 +124,83 @@ export const RoomDirectorySaveButton = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
{...props}
|
||||||
label="resources.room_directory.action.create"
|
label="resources.room_directory.action.create"
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={loading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<FolderSharedIcon />
|
<RoomDirectoryIcon />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomDirectoryBulkActionButtons = props => (
|
const RoomDirectoryListActions = () => (
|
||||||
<Fragment>
|
<TopToolbar>
|
||||||
<RoomDirectoryBulkDeleteButton {...props} />
|
<SelectColumnsButton />
|
||||||
</Fragment>
|
<ExportButton />
|
||||||
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AvatarField = ({ source, className, record = {} }) => (
|
export const RoomDirectoryList = () => (
|
||||||
<Avatar src={record[source]} className={className} />
|
<List
|
||||||
);
|
pagination={<RoomDirectoryPagination />}
|
||||||
|
perPage={100}
|
||||||
const RoomDirectoryFilter = ({ ...props }) => {
|
actions={<RoomDirectoryListActions />}
|
||||||
const translate = useTranslate();
|
>
|
||||||
return (
|
<DatagridConfigurable
|
||||||
<Filter {...props}>
|
rowClick={(id, _resource, _record) => "/rooms/" + id + "/show"}
|
||||||
<Chip
|
bulkActionButtons={<RoomDirectoryBulkUnpublishButton />}
|
||||||
label={translate("resources.rooms.fields.room_id")}
|
omit={["room_id", "canonical_alias", "topic"]}
|
||||||
source="room_id"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.topic")}
|
|
||||||
source="topic"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.canonical_alias")}
|
|
||||||
source="canonical_alias"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
</Filter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FilterableRoomDirectoryList = ({
|
|
||||||
roomDirectoryFilters,
|
|
||||||
dispatch,
|
|
||||||
...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;
|
|
||||||
const canonicalAliasFilter = filter && filter.canonical_alias ? true : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
{...props}
|
|
||||||
pagination={<RoomDirectoryPagination />}
|
|
||||||
bulkActionButtons={<RoomDirectoryBulkActionButtons />}
|
|
||||||
filters={<RoomDirectoryFilter />}
|
|
||||||
perPage={100}
|
|
||||||
>
|
>
|
||||||
<Datagrid rowClick={(id, basePath, record) => "/rooms/" + id + "/show"}>
|
<AvatarField
|
||||||
<AvatarField
|
source="avatar_src"
|
||||||
source="avatar_src"
|
sortable={false}
|
||||||
sortable={false}
|
sx={{ height: "40px", width: "40px" }}
|
||||||
className={classes.small}
|
label="resources.rooms.fields.avatar"
|
||||||
label={translate("resources.rooms.fields.avatar")}
|
/>
|
||||||
/>
|
<TextField
|
||||||
<TextField
|
source="name"
|
||||||
source="name"
|
sortable={false}
|
||||||
sortable={false}
|
label="resources.rooms.fields.name"
|
||||||
label={translate("resources.rooms.fields.name")}
|
/>
|
||||||
/>
|
<TextField
|
||||||
{roomIdFilter && (
|
source="room_id"
|
||||||
<TextField
|
sortable={false}
|
||||||
source="room_id"
|
label="resources.rooms.fields.room_id"
|
||||||
sortable={false}
|
/>
|
||||||
label={translate("resources.rooms.fields.room_id")}
|
<TextField
|
||||||
/>
|
source="canonical_alias"
|
||||||
)}
|
sortable={false}
|
||||||
{canonicalAliasFilter && (
|
label="resources.rooms.fields.canonical_alias"
|
||||||
<TextField
|
/>
|
||||||
source="canonical_alias"
|
<TextField
|
||||||
sortable={false}
|
source="topic"
|
||||||
label={translate("resources.rooms.fields.canonical_alias")}
|
sortable={false}
|
||||||
/>
|
label="resources.rooms.fields.topic"
|
||||||
)}
|
/>
|
||||||
{topicFilter && (
|
<NumberField
|
||||||
<TextField
|
source="num_joined_members"
|
||||||
source="topic"
|
sortable={false}
|
||||||
sortable={false}
|
label="resources.rooms.fields.joined_members"
|
||||||
label={translate("resources.rooms.fields.topic")}
|
/>
|
||||||
/>
|
<BooleanField
|
||||||
)}
|
source="world_readable"
|
||||||
<NumberField
|
sortable={false}
|
||||||
source="num_joined_members"
|
label="resources.room_directory.fields.world_readable"
|
||||||
sortable={false}
|
/>
|
||||||
label={translate("resources.rooms.fields.joined_members")}
|
<BooleanField
|
||||||
/>
|
source="guest_can_join"
|
||||||
<BooleanField
|
sortable={false}
|
||||||
source="world_readable"
|
label="resources.room_directory.fields.guest_can_join"
|
||||||
sortable={false}
|
/>
|
||||||
label={translate("resources.room_directory.fields.world_readable")}
|
</DatagridConfigurable>
|
||||||
/>
|
</List>
|
||||||
<BooleanField
|
);
|
||||||
source="guest_can_join"
|
|
||||||
sortable={false}
|
const resource = {
|
||||||
label={translate("resources.room_directory.fields.guest_can_join")}
|
name: "room_directory",
|
||||||
/>
|
icon: RoomDirectoryIcon,
|
||||||
</Datagrid>
|
list: RoomDirectoryList,
|
||||||
</List>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
export default resource;
|
||||||
return {
|
|
||||||
roomDirectoryFilters:
|
|
||||||
state.admin.resources.room_directory.list.params.displayedFilters,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RoomDirectoryList = connect(mapStateToProps)(
|
|
||||||
FilterableRoomDirectoryList
|
|
||||||
);
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
@ -7,20 +7,24 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
required,
|
required,
|
||||||
useCreate,
|
useCreate,
|
||||||
useMutation,
|
useDataProvider,
|
||||||
|
useListContext,
|
||||||
useNotify,
|
useNotify,
|
||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
useUnselectAll,
|
useUnselectAll,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import MessageIcon from "@material-ui/icons/Message";
|
import { useMutation } from "react-query";
|
||||||
import IconCancel from "@material-ui/icons/Cancel";
|
import MessageIcon from "@mui/icons-material/Message";
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
import IconCancel from "@mui/icons-material/Cancel";
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
import {
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
Dialog,
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
const ServerNoticeDialog = ({ open, loading, onClose, onSubmit }) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
const ServerNoticeToolbar = props => (
|
const ServerNoticeToolbar = props => (
|
||||||
@ -44,12 +48,7 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
{translate("resources.servernotices.helper.send")}
|
{translate("resources.servernotices.helper.send")}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<SimpleForm
|
<SimpleForm toolbar={<ServerNoticeToolbar />} onSubmit={onSubmit}>
|
||||||
toolbar={<ServerNoticeToolbar />}
|
|
||||||
submitOnEnter={false}
|
|
||||||
redirect={false}
|
|
||||||
save={onSend}
|
|
||||||
>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
source="body"
|
source="body"
|
||||||
label="resources.servernotices.fields.body"
|
label="resources.servernotices.fields.body"
|
||||||
@ -65,24 +64,25 @@ const ServerNoticeDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServerNoticeButton = props => {
|
export const ServerNoticeButton = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [create, { loading }] = useCreate("servernotices");
|
const [create, { isloading }] = useCreate();
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const handleDialogOpen = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const handleDialogClose = () => setOpen(false);
|
||||||
|
|
||||||
const handleSend = values => {
|
const handleSend = values => {
|
||||||
create(
|
create(
|
||||||
{ payload: { data: { id: record.id, ...values } } },
|
"servernotices",
|
||||||
|
{ data: { id: record.id, ...values } },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.servernotices.action.send_success");
|
notify("resources.servernotices.action.send_success");
|
||||||
handleDialogClose();
|
handleDialogClose();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.servernotices.action.send_failure", {
|
notify("resources.servernotices.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -91,67 +91,65 @@ export const ServerNoticeButton = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label="resources.servernotices.send"
|
label="resources.servernotices.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={handleDialogOpen}
|
||||||
disabled={loading}
|
disabled={isloading}
|
||||||
>
|
>
|
||||||
<MessageIcon />
|
<MessageIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<ServerNoticeDialog
|
<ServerNoticeDialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleDialogClose}
|
onClose={handleDialogClose}
|
||||||
onSend={handleSend}
|
onSubmit={handleSend}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServerNoticeBulkButton = ({ selectedIds }) => {
|
export const ServerNoticeBulkButton = () => {
|
||||||
|
const { selectedIds } = useListContext();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const openDialog = () => setOpen(true);
|
||||||
|
const closeDialog = () => setOpen(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const unselectAll = useUnselectAll();
|
const unselectAllUsers = useUnselectAll("users");
|
||||||
const [createMany, { loading }] = useMutation();
|
const dataProvider = useDataProvider();
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const { mutate: sendNotices, isLoading } = useMutation(
|
||||||
const handleDialogClose = () => setOpen(false);
|
data =>
|
||||||
|
dataProvider.createMany("servernotices", {
|
||||||
const handleSend = values => {
|
ids: selectedIds,
|
||||||
createMany(
|
data: data,
|
||||||
{
|
}),
|
||||||
type: "createMany",
|
{
|
||||||
resource: "servernotices",
|
onSuccess: () => {
|
||||||
payload: { ids: selectedIds, data: values },
|
notify("resources.servernotices.action.send_success");
|
||||||
|
unselectAllUsers();
|
||||||
|
closeDialog();
|
||||||
},
|
},
|
||||||
{
|
onError: () =>
|
||||||
onSuccess: ({ data }) => {
|
notify("resources.servernotices.action.send_failure", {
|
||||||
notify("resources.servernotices.action.send_success");
|
type: "error",
|
||||||
unselectAll("users");
|
}),
|
||||||
handleDialogClose();
|
}
|
||||||
},
|
);
|
||||||
onFailure: error =>
|
|
||||||
notify("resources.servernotices.action.send_failure", {
|
|
||||||
type: "error",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label="resources.servernotices.send"
|
label="resources.servernotices.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={openDialog}
|
||||||
disabled={loading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<MessageIcon />
|
<MessageIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<ServerNoticeDialog
|
<ServerNoticeDialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleDialogClose}
|
onClose={closeDialog}
|
||||||
onSend={handleSend}
|
onSubmit={sendNotices}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Datagrid,
|
Datagrid,
|
||||||
DateField,
|
DateField,
|
||||||
Filter,
|
|
||||||
List,
|
List,
|
||||||
Pagination,
|
Pagination,
|
||||||
ReferenceField,
|
ReferenceField,
|
||||||
@ -20,12 +19,13 @@ import {
|
|||||||
useRefresh,
|
useRefresh,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import AutorenewIcon from "@material-ui/icons/Autorenew";
|
import AutorenewIcon from "@mui/icons-material/Autorenew";
|
||||||
import FolderSharedIcon from "@material-ui/icons/FolderShared";
|
import DestinationsIcon from "@mui/icons-material/CloudQueue";
|
||||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
import FolderSharedIcon from "@mui/icons-material/FolderShared";
|
||||||
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
|
|
||||||
const DestinationPagination = props => (
|
const DestinationPagination = () => (
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const date_format = {
|
const date_format = {
|
||||||
@ -37,23 +37,17 @@ const date_format = {
|
|||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
};
|
};
|
||||||
|
|
||||||
const destinationRowStyle = (record, index) => ({
|
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();
|
||||||
const [handleReconnect, { isLoading }] = useDelete("destinations");
|
const [handleReconnect, { isLoading }] = useDelete();
|
||||||
|
|
||||||
// Reconnect is not required if no error has occurred. (`failure_ts`)
|
// Reconnect is not required if no error has occurred. (`failure_ts`)
|
||||||
if (!record || !record.failure_ts) return null;
|
if (!record || !record.failure_ts) return null;
|
||||||
@ -63,7 +57,8 @@ export const DestinationReconnectButton = props => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
handleReconnect(
|
handleReconnect(
|
||||||
{ payload: { id: record.id } },
|
"destinations",
|
||||||
|
{ id: record.id },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("ra.notification.updated", {
|
notify("ra.notification.updated", {
|
||||||
@ -71,7 +66,7 @@ export const DestinationReconnectButton = props => {
|
|||||||
});
|
});
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () => {
|
onError: () => {
|
||||||
notify("ra.message.error", { type: "error" });
|
notify("ra.message.error", { type: "error" });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -89,13 +84,13 @@ export const DestinationReconnectButton = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DestinationShowActions = props => (
|
const DestinationShowActions = () => (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
<DestinationReconnectButton />
|
<DestinationReconnectButton />
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
|
|
||||||
const DestinationTitle = props => {
|
const DestinationTitle = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
@ -109,14 +104,14 @@ export const DestinationList = props => {
|
|||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
{...props}
|
{...props}
|
||||||
filters={<DestinationFilter />}
|
filters={destinationFilters}
|
||||||
pagination={<DestinationPagination />}
|
pagination={<DestinationPagination />}
|
||||||
sort={{ field: "destination", order: "ASC" }}
|
sort={{ field: "destination", order: "ASC" }}
|
||||||
bulkActionButtons={false}
|
|
||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
rowStyle={destinationRowStyle}
|
rowSx={destinationRowSx}
|
||||||
rowClick={(id, basePath, record) => `${basePath}/${id}/show/rooms`}
|
rowClick={(id, _resource, _record) => `${id}/show/rooms`}
|
||||||
|
bulkActionButtons={false}
|
||||||
>
|
>
|
||||||
<TextField source="destination" />
|
<TextField source="destination" />
|
||||||
<DateField source="failure_ts" showTime options={date_format} />
|
<DateField source="failure_ts" showTime options={date_format} />
|
||||||
@ -160,7 +155,7 @@ export const DestinationShow = props => {
|
|||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, basePath, record) => `/rooms/${id}/show`}
|
rowClick={(id, resource, record) => `/rooms/${id}/show`}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="room_id"
|
source="room_id"
|
||||||
@ -183,3 +178,12 @@ export const DestinationShow = props => {
|
|||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resource = {
|
||||||
|
name: "destinations",
|
||||||
|
icon: DestinationsIcon,
|
||||||
|
list: DestinationList,
|
||||||
|
show: DestinationShow,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resource;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
useDelete,
|
useDelete,
|
||||||
@ -7,35 +7,17 @@ import {
|
|||||||
useRecordContext,
|
useRecordContext,
|
||||||
useRefresh,
|
useRefresh,
|
||||||
} from "react-admin";
|
} 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, useTheme } from "@mui/material/styles";
|
||||||
import { alpha } from "@material-ui/core/styles/colorManipulator";
|
|
||||||
import classnames from "classnames";
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
deleteButton: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
|
||||||
// Reset on mouse devices
|
|
||||||
"@media (hover: none)": {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "RaDeleteDeviceButton" }
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DeviceRemoveButton = props => {
|
export const DeviceRemoveButton = props => {
|
||||||
|
const theme = useTheme();
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const classes = useStyles(props);
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
|
|
||||||
const [removeDevice, { isLoading }] = useDelete("devices");
|
const [removeDevice, { isLoading }] = useDelete();
|
||||||
|
|
||||||
if (!record) return null;
|
if (!record) return null;
|
||||||
|
|
||||||
@ -44,13 +26,15 @@ export const DeviceRemoveButton = props => {
|
|||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
removeDevice(
|
removeDevice(
|
||||||
{ payload: { id: record.id, user_id: record.user_id } },
|
"devices",
|
||||||
|
// needs previousData for user_id
|
||||||
|
{ id: record.id, previousData: record },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.devices.action.erase.success");
|
notify("resources.devices.action.erase.success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () => {
|
onError: () => {
|
||||||
notify("resources.devices.action.erase.failure", { type: "error" });
|
notify("resources.devices.action.erase.failure", { type: "error" });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -59,11 +43,21 @@ export const DeviceRemoveButton = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
{...props}
|
||||||
label="ra.action.remove"
|
label="ra.action.remove"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={classnames("ra-delete-button", classes.deleteButton)}
|
sx={{
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
||||||
|
// Reset on mouse devices
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ActionDelete />
|
<ActionDelete />
|
||||||
</Button>
|
</Button>
|
||||||
@ -79,6 +73,6 @@ export const DeviceRemoveButton = props => {
|
|||||||
name: record.display_name ? record.display_name : record.id,
|
name: record.display_name ? record.display_name : record.id,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import React, { Fragment, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import classnames from "classnames";
|
|
||||||
import { alpha } from "@material-ui/core/styles/colorManipulator";
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
import { Tooltip } from "@material-ui/core";
|
|
||||||
import {
|
import {
|
||||||
BooleanInput,
|
BooleanInput,
|
||||||
Button,
|
Button,
|
||||||
@ -18,34 +14,22 @@ import {
|
|||||||
useRefresh,
|
useRefresh,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import BlockIcon from "@material-ui/icons/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import ClearIcon from "@material-ui/icons/Clear";
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
import DeleteSweepIcon from "@material-ui/icons/DeleteSweep";
|
import DeleteSweepIcon from "@mui/icons-material/DeleteSweep";
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
import {
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
Dialog,
|
||||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
DialogContent,
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
DialogContentText,
|
||||||
import IconCancel from "@material-ui/icons/Cancel";
|
DialogTitle,
|
||||||
import LockIcon from "@material-ui/icons/Lock";
|
Tooltip,
|
||||||
import LockOpenIcon from "@material-ui/icons/LockOpen";
|
} from "@mui/material";
|
||||||
|
import IconCancel from "@mui/icons-material/Cancel";
|
||||||
|
import LockIcon from "@mui/icons-material/Lock";
|
||||||
|
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||||
|
import { alpha, useTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const DeleteMediaDialog = ({ open, loading, onClose, onSubmit }) => {
|
||||||
theme => ({
|
|
||||||
deleteButton: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
|
||||||
// Reset on mouse devices
|
|
||||||
"@media (hover: none)": {
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "RaDeleteDeviceButton" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
||||||
const dateParser = v => {
|
const dateParser = v => {
|
||||||
@ -54,19 +38,17 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
return d.getTime();
|
return d.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeleteMediaToolbar = props => {
|
const DeleteMediaToolbar = props => (
|
||||||
return (
|
<Toolbar {...props}>
|
||||||
<Toolbar {...props}>
|
<SaveButton
|
||||||
<SaveButton
|
label="resources.delete_media.action.send"
|
||||||
label="resources.delete_media.action.send"
|
icon={<DeleteSweepIcon />}
|
||||||
icon={<DeleteSweepIcon />}
|
/>
|
||||||
/>
|
<Button label="ra.action.cancel" onClick={onClose}>
|
||||||
<Button label="ra.action.cancel" onClick={onClose}>
|
<IconCancel />
|
||||||
<IconCancel />
|
</Button>
|
||||||
</Button>
|
</Toolbar>
|
||||||
</Toolbar>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} loading={loading}>
|
<Dialog open={open} onClose={onClose} loading={loading}>
|
||||||
@ -77,12 +59,7 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
{translate("resources.delete_media.helper.send")}
|
{translate("resources.delete_media.helper.send")}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<SimpleForm
|
<SimpleForm toolbar={<DeleteMediaToolbar />} onSubmit={onSubmit}>
|
||||||
toolbar={<DeleteMediaToolbar />}
|
|
||||||
submitOnEnter={false}
|
|
||||||
redirect={false}
|
|
||||||
save={onSend}
|
|
||||||
>
|
|
||||||
<DateTimeInput
|
<DateTimeInput
|
||||||
fullWidth
|
fullWidth
|
||||||
source="before_ts"
|
source="before_ts"
|
||||||
@ -111,23 +88,25 @@ const DeleteMediaDialog = ({ open, loading, onClose, onSend }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteMediaButton = props => {
|
export const DeleteMediaButton = props => {
|
||||||
const classes = useStyles(props);
|
const theme = useTheme();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [deleteOne, { loading }] = useDelete("delete_media");
|
const [deleteOne, { isLoading }] = useDelete();
|
||||||
|
|
||||||
const handleDialogOpen = () => setOpen(true);
|
const openDialog = () => setOpen(true);
|
||||||
const handleDialogClose = () => setOpen(false);
|
const closeDialog = () => setOpen(false);
|
||||||
|
|
||||||
const handleSend = values => {
|
const deleteMedia = values => {
|
||||||
deleteOne(
|
deleteOne(
|
||||||
{ payload: { ...values } },
|
"delete_media",
|
||||||
|
// needs meta.before_ts, meta.size_gt and meta.keep_profiles
|
||||||
|
{ meta: values },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.delete_media.action.send_success");
|
notify("resources.delete_media.action.send_success");
|
||||||
handleDialogClose();
|
closeDialog();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.delete_media.action.send_failure", {
|
notify("resources.delete_media.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -136,43 +115,54 @@ export const DeleteMediaButton = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
{...props}
|
||||||
label="resources.delete_media.action.send"
|
label="resources.delete_media.action.send"
|
||||||
onClick={handleDialogOpen}
|
onClick={openDialog}
|
||||||
disabled={loading}
|
disabled={isLoading}
|
||||||
className={classnames("ra-delete-button", classes.deleteButton)}
|
sx={{
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: alpha(theme.palette.error.main, 0.12),
|
||||||
|
// Reset on mouse devices
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DeleteSweepIcon />
|
<DeleteSweepIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<DeleteMediaDialog
|
<DeleteMediaDialog
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleDialogClose}
|
onClose={closeDialog}
|
||||||
onSend={handleSend}
|
onSubmit={deleteMedia}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProtectMediaButton = props => {
|
export const ProtectMediaButton = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [create, { loading }] = useCreate("protect_media");
|
const [create, { isLoading }] = useCreate();
|
||||||
const [deleteOne] = useDelete("protect_media");
|
const [deleteOne] = useDelete();
|
||||||
|
|
||||||
if (!record) return null;
|
if (!record) return null;
|
||||||
|
|
||||||
const handleProtect = () => {
|
const handleProtect = () => {
|
||||||
create(
|
create(
|
||||||
{ payload: { data: record } },
|
"protect_media",
|
||||||
|
{ data: record },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.protect_media.action.send_success");
|
notify("resources.protect_media.action.send_success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.protect_media.action.send_failure", {
|
notify("resources.protect_media.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -182,13 +172,14 @@ export const ProtectMediaButton = props => {
|
|||||||
|
|
||||||
const handleUnprotect = () => {
|
const handleUnprotect = () => {
|
||||||
deleteOne(
|
deleteOne(
|
||||||
{ payload: { ...record } },
|
"protect_media",
|
||||||
|
{ id: record.id },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.protect_media.action.send_success");
|
notify("resources.protect_media.action.send_success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.protect_media.action.send_failure", {
|
notify("resources.protect_media.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -201,7 +192,7 @@ export const ProtectMediaButton = props => {
|
|||||||
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", {
|
||||||
@ -227,7 +218,7 @@ export const ProtectMediaButton = props => {
|
|||||||
arrow
|
arrow
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleUnprotect} disabled={loading}>
|
<Button onClick={handleUnprotect} disabled={isLoading}>
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -240,13 +231,13 @@ export const ProtectMediaButton = props => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleProtect} disabled={loading}>
|
<Button onClick={handleProtect} disabled={isLoading}>
|
||||||
<LockOpenIcon />
|
<LockOpenIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -255,20 +246,21 @@ export const QuarantineMediaButton = props => {
|
|||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [create, { loading }] = useCreate("quarantine_media");
|
const [create, { isLoading }] = useCreate();
|
||||||
const [deleteOne] = useDelete("quarantine_media");
|
const [deleteOne] = useDelete();
|
||||||
|
|
||||||
if (!record) return null;
|
if (!record) return null;
|
||||||
|
|
||||||
const handleQuarantaine = () => {
|
const handleQuarantaine = () => {
|
||||||
create(
|
create(
|
||||||
{ payload: { data: record } },
|
"quarantine_media",
|
||||||
|
{ data: record },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.quarantine_media.action.send_success");
|
notify("resources.quarantine_media.action.send_success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.quarantine_media.action.send_failure", {
|
notify("resources.quarantine_media.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -278,13 +270,14 @@ export const QuarantineMediaButton = props => {
|
|||||||
|
|
||||||
const handleRemoveQuarantaine = () => {
|
const handleRemoveQuarantaine = () => {
|
||||||
deleteOne(
|
deleteOne(
|
||||||
{ payload: { ...record } },
|
"quarantine_media",
|
||||||
|
{ id: record.id, previousData: record },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notify("resources.quarantine_media.action.send_success");
|
notify("resources.quarantine_media.action.send_success");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onFailure: () =>
|
onError: () =>
|
||||||
notify("resources.quarantine_media.action.send_failure", {
|
notify("resources.quarantine_media.action.send_failure", {
|
||||||
type: "error",
|
type: "error",
|
||||||
}),
|
}),
|
||||||
@ -293,7 +286,7 @@ export const QuarantineMediaButton = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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", {
|
||||||
@ -301,7 +294,7 @@ export const QuarantineMediaButton = props => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button disabled={true}>
|
<Button {...props} disabled={true}>
|
||||||
<ClearIcon />
|
<ClearIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -314,7 +307,11 @@ export const QuarantineMediaButton = props => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleRemoveQuarantaine} disabled={loading}>
|
<Button
|
||||||
|
{...props}
|
||||||
|
onClick={handleRemoveQuarantaine}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
<BlockIcon color="error" />
|
<BlockIcon color="error" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -327,12 +324,12 @@ export const QuarantineMediaButton = props => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleQuarantaine} disabled={loading}>
|
<Button onClick={handleQuarantaine} disabled={isLoading}>
|
||||||
<BlockIcon />
|
<BlockIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import React, { Fragment } from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import {
|
import {
|
||||||
BooleanField,
|
BooleanField,
|
||||||
BulkDeleteButton,
|
BulkDeleteButton,
|
||||||
DateField,
|
DateField,
|
||||||
Datagrid,
|
Datagrid,
|
||||||
|
DatagridConfigurable,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
Filter,
|
ExportButton,
|
||||||
|
FunctionField,
|
||||||
List,
|
List,
|
||||||
NumberField,
|
NumberField,
|
||||||
Pagination,
|
Pagination,
|
||||||
ReferenceField,
|
ReferenceField,
|
||||||
ReferenceManyField,
|
ReferenceManyField,
|
||||||
SearchInput,
|
SearchInput,
|
||||||
|
SelectColumnsButton,
|
||||||
SelectField,
|
SelectField,
|
||||||
Show,
|
Show,
|
||||||
Tab,
|
Tab,
|
||||||
@ -22,23 +24,22 @@ import {
|
|||||||
useRecordContext,
|
useRecordContext,
|
||||||
useTranslate,
|
useTranslate,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import get from "lodash/get";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import PropTypes from "prop-types";
|
import Box from "@mui/material/Box";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import FastForwardIcon from "@mui/icons-material/FastForward";
|
||||||
import { Tooltip, Typography, Chip } from "@material-ui/core";
|
import HttpsIcon from "@mui/icons-material/Https";
|
||||||
import FastForwardIcon from "@material-ui/icons/FastForward";
|
import NoEncryptionIcon from "@mui/icons-material/NoEncryption";
|
||||||
import HttpsIcon from "@material-ui/icons/Https";
|
import PageviewIcon from "@mui/icons-material/Pageview";
|
||||||
import NoEncryptionIcon from "@material-ui/icons/NoEncryption";
|
import UserIcon from "@mui/icons-material/Group";
|
||||||
import PageviewIcon from "@material-ui/icons/Pageview";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
import UserIcon from "@material-ui/icons/Group";
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
import EventIcon from "@mui/icons-material/Event";
|
||||||
import VisibilityIcon from "@material-ui/icons/Visibility";
|
import RoomIcon from "@mui/icons-material/ViewList";
|
||||||
import EventIcon from "@material-ui/icons/Event";
|
|
||||||
import {
|
import {
|
||||||
RoomDirectoryBulkDeleteButton,
|
RoomDirectoryBulkUnpublishButton,
|
||||||
RoomDirectoryBulkSaveButton,
|
RoomDirectoryBulkPublishButton,
|
||||||
RoomDirectoryDeleteButton,
|
RoomDirectoryUnpublishButton,
|
||||||
RoomDirectorySaveButton,
|
RoomDirectoryPublishButton,
|
||||||
} from "./RoomDirectory";
|
} from "./RoomDirectory";
|
||||||
|
|
||||||
const date_format = {
|
const date_format = {
|
||||||
@ -50,44 +51,11 @@ const date_format = {
|
|||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const RoomPagination = () => (
|
||||||
helper_forward_extremities: {
|
<Pagination rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
||||||
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
|
||||||
margin: "0.5em",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const RoomPagination = props => (
|
|
||||||
<Pagination {...props} rowsPerPageOptions={[10, 25, 50, 100, 500, 1000]} />
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const EncryptionField = ({ source, record = {}, emptyText }) => {
|
const RoomTitle = () => {
|
||||||
const translate = useTranslate();
|
|
||||||
const value = get(record, source);
|
|
||||||
let ariaLabel = value === false ? "ra.boolean.false" : "ra.boolean.true";
|
|
||||||
|
|
||||||
if (value === false || value === true) {
|
|
||||||
return (
|
|
||||||
<Typography component="span" variant="body2">
|
|
||||||
<Tooltip title={translate(ariaLabel, { _: ariaLabel })}>
|
|
||||||
{value === true ? (
|
|
||||||
<HttpsIcon data-testid="true" htmlColor="limegreen" />
|
|
||||||
) : (
|
|
||||||
<NoEncryptionIcon data-testid="false" color="error" />
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Typography component="span" variant="body2">
|
|
||||||
{emptyText}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomTitle = props => {
|
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
var name = "";
|
var name = "";
|
||||||
@ -102,24 +70,18 @@ const RoomTitle = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomShowActions = ({ basePath, data, resource }) => {
|
const RoomShowActions = () => {
|
||||||
|
const record = useRecordContext();
|
||||||
var roomDirectoryStatus = "";
|
var roomDirectoryStatus = "";
|
||||||
if (data) {
|
if (record) {
|
||||||
roomDirectoryStatus = data.public;
|
roomDirectoryStatus = record.public;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar>
|
<TopToolbar>
|
||||||
{roomDirectoryStatus === false && (
|
{roomDirectoryStatus === false && <RoomDirectoryPublishButton />}
|
||||||
<RoomDirectorySaveButton record={data} />
|
{roomDirectoryStatus === true && <RoomDirectoryUnpublishButton />}
|
||||||
)}
|
|
||||||
{roomDirectoryStatus === true && (
|
|
||||||
<RoomDirectoryDeleteButton record={data} />
|
|
||||||
)}
|
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
basePath={basePath}
|
|
||||||
record={data}
|
|
||||||
resource={resource}
|
|
||||||
mutationMode="pessimistic"
|
mutationMode="pessimistic"
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
confirmTitle="resources.rooms.action.erase.title"
|
||||||
confirmContent="resources.rooms.action.erase.content"
|
confirmContent="resources.rooms.action.erase.content"
|
||||||
@ -129,7 +91,6 @@ const RoomShowActions = ({ basePath, data, resource }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RoomShow = props => {
|
export const RoomShow = props => {
|
||||||
const classes = useStyles({ props });
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
|
<Show {...props} actions={<RoomShowActions />} title={<RoomTitle />}>
|
||||||
@ -171,7 +132,7 @@ export const RoomShow = props => {
|
|||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, basePath, record) => "/users/" + id}
|
rowClick={(id, resource, record) => "/users/" + id}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
@ -281,9 +242,14 @@ export const RoomShow = props => {
|
|||||||
icon={<FastForwardIcon />}
|
icon={<FastForwardIcon />}
|
||||||
path="forward_extremities"
|
path="forward_extremities"
|
||||||
>
|
>
|
||||||
<div className={classes.helper_forward_extremities}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
|
||||||
|
margin: "0.5em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{translate("resources.rooms.helper.forward_extremities")}
|
{translate("resources.rooms.helper.forward_extremities")}
|
||||||
</div>
|
</Box>
|
||||||
<ReferenceManyField
|
<ReferenceManyField
|
||||||
reference="forward_extremities"
|
reference="forward_extremities"
|
||||||
target="room_id"
|
target="room_id"
|
||||||
@ -307,104 +273,81 @@ export const RoomShow = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomBulkActionButtons = props => (
|
const RoomBulkActionButtons = () => (
|
||||||
<Fragment>
|
<>
|
||||||
<RoomDirectoryBulkSaveButton {...props} />
|
<RoomDirectoryBulkPublishButton />
|
||||||
<RoomDirectoryBulkDeleteButton {...props} />
|
<RoomDirectoryBulkUnpublishButton />
|
||||||
<BulkDeleteButton
|
<BulkDeleteButton
|
||||||
{...props}
|
|
||||||
confirmTitle="resources.rooms.action.erase.title"
|
confirmTitle="resources.rooms.action.erase.title"
|
||||||
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 />];
|
||||||
const translate = useTranslate();
|
|
||||||
return (
|
|
||||||
<Filter {...props}>
|
|
||||||
<SearchInput source="search_term" alwaysOn />
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.joined_local_members")}
|
|
||||||
source="joined_local_members"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.state_events")}
|
|
||||||
source="state_events"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.version")}
|
|
||||||
source="version"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
label={translate("resources.rooms.fields.federatable")}
|
|
||||||
source="federatable"
|
|
||||||
defaultValue={false}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
/>
|
|
||||||
</Filter>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomNameField = props => {
|
const RoomListActions = () => (
|
||||||
const { source } = props;
|
<TopToolbar>
|
||||||
const record = useRecordContext();
|
<SelectColumnsButton />
|
||||||
return (
|
<ExportButton />
|
||||||
<span>{record[source] || record["canonical_alias"] || record["id"]}</span>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
RoomNameField.propTypes = {
|
export const RoomList = props => {
|
||||||
label: PropTypes.string,
|
const theme = useTheme();
|
||||||
record: PropTypes.object,
|
|
||||||
source: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FilterableRoomList = ({ roomFilters, dispatch, ...props }) => {
|
|
||||||
const filter = roomFilters;
|
|
||||||
const localMembersFilter =
|
|
||||||
filter && filter.joined_local_members ? true : false;
|
|
||||||
const stateEventsFilter = filter && filter.state_events ? true : false;
|
|
||||||
const versionFilter = filter && filter.version ? true : false;
|
|
||||||
const federateableFilter = filter && filter.federatable ? true : false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
{...props}
|
{...props}
|
||||||
pagination={<RoomPagination />}
|
pagination={<RoomPagination />}
|
||||||
sort={{ field: "name", order: "ASC" }}
|
sort={{ field: "name", order: "ASC" }}
|
||||||
filters={<RoomFilter />}
|
filters={roomFilters}
|
||||||
bulkActionButtons={<RoomBulkActionButtons />}
|
actions={<RoomListActions />}
|
||||||
>
|
>
|
||||||
<Datagrid rowClick="show">
|
<DatagridConfigurable
|
||||||
<EncryptionField
|
rowClick="show"
|
||||||
|
bulkActionButtons={<RoomBulkActionButtons />}
|
||||||
|
omit={[
|
||||||
|
"joined_local_members",
|
||||||
|
"state_events",
|
||||||
|
"version",
|
||||||
|
"federatable",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<BooleanField
|
||||||
source="is_encrypted"
|
source="is_encrypted"
|
||||||
sortBy="encryption"
|
sortBy="encryption"
|
||||||
|
TrueIcon={HttpsIcon}
|
||||||
|
FalseIcon={NoEncryptionIcon}
|
||||||
label={<HttpsIcon />}
|
label={<HttpsIcon />}
|
||||||
|
sx={{
|
||||||
|
[`& [data-testid="true"]`]: { color: theme.palette.success.main },
|
||||||
|
[`& [data-testid="false"]`]: { color: theme.palette.error.main },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FunctionField
|
||||||
|
source="name"
|
||||||
|
render={record =>
|
||||||
|
record["name"] || record["canonical_alias"] || record["id"]
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<RoomNameField source="name" />
|
|
||||||
<TextField source="joined_members" />
|
<TextField source="joined_members" />
|
||||||
{localMembersFilter && <TextField source="joined_local_members" />}
|
<TextField source="joined_local_members" />
|
||||||
{stateEventsFilter && <TextField source="state_events" />}
|
<TextField source="state_events" />
|
||||||
{versionFilter && <TextField source="version" />}
|
<TextField source="version" />
|
||||||
{federateableFilter && <BooleanField source="federatable" />}
|
<BooleanField source="federatable" />
|
||||||
<BooleanField source="public" />
|
<BooleanField source="public" />
|
||||||
</Datagrid>
|
</DatagridConfigurable>
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
const resource = {
|
||||||
return {
|
name: "rooms",
|
||||||
roomFilters: state.admin.resources.rooms.list.params.displayedFilters,
|
icon: RoomIcon,
|
||||||
};
|
list: RoomList,
|
||||||
}
|
show: RoomShow,
|
||||||
|
};
|
||||||
|
|
||||||
export const RoomList = connect(mapStateToProps)(FilterableRoomList);
|
export default resource;
|
||||||
|
@ -3,7 +3,6 @@ import { cloneElement } from "react";
|
|||||||
import {
|
import {
|
||||||
Datagrid,
|
Datagrid,
|
||||||
ExportButton,
|
ExportButton,
|
||||||
Filter,
|
|
||||||
List,
|
List,
|
||||||
NumberField,
|
NumberField,
|
||||||
Pagination,
|
Pagination,
|
||||||
@ -13,18 +12,13 @@ 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 => {
|
||||||
const { className, exporter, filters, maxResults, ...rest } = props;
|
const { className, exporter, filters, maxResults, ...rest } = props;
|
||||||
const {
|
const { sort, resource, displayedFilters, filterValues, showFilter, total } =
|
||||||
currentSort,
|
useListContext();
|
||||||
resource,
|
|
||||||
displayedFilters,
|
|
||||||
filterValues,
|
|
||||||
showFilter,
|
|
||||||
total,
|
|
||||||
} = useListContext();
|
|
||||||
return (
|
return (
|
||||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||||
{filters &&
|
{filters &&
|
||||||
@ -39,7 +33,7 @@ const ListActions = props => {
|
|||||||
<ExportButton
|
<ExportButton
|
||||||
disabled={total === 0}
|
disabled={total === 0}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
sort={currentSort}
|
sort={sort}
|
||||||
filterValues={filterValues}
|
filterValues={filterValues}
|
||||||
maxResults={maxResults}
|
maxResults={maxResults}
|
||||||
/>
|
/>
|
||||||
@ -47,35 +41,39 @@ const ListActions = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserMediaStatsPagination = props => (
|
const UserMediaStatsPagination = () => (
|
||||||
<Pagination {...props} 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 />
|
|
||||||
</Filter>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UserMediaStatsList = props => {
|
export const UserMediaStatsList = props => (
|
||||||
return (
|
<List
|
||||||
<List
|
{...props}
|
||||||
{...props}
|
actions={<ListActions />}
|
||||||
actions={<ListActions />}
|
filters={userMediaStatsFilters}
|
||||||
filters={<UserMediaStatsFilter />}
|
pagination={<UserMediaStatsPagination />}
|
||||||
pagination={<UserMediaStatsPagination />}
|
sort={{ field: "media_length", order: "DESC" }}
|
||||||
sort={{ field: "media_length", order: "DESC" }}
|
>
|
||||||
|
<Datagrid
|
||||||
|
rowClick={(id, resource, record) => "/users/" + id + "/media"}
|
||||||
bulkActionButtons={false}
|
bulkActionButtons={false}
|
||||||
>
|
>
|
||||||
<Datagrid rowClick={(id, basePath, record) => "/users/" + id + "/media"}>
|
<TextField source="user_id" label="resources.users.fields.id" />
|
||||||
<TextField source="user_id" label="resources.users.fields.id" />
|
<TextField
|
||||||
<TextField
|
source="displayname"
|
||||||
source="displayname"
|
label="resources.users.fields.displayname"
|
||||||
label="resources.users.fields.displayname"
|
/>
|
||||||
/>
|
<NumberField source="media_count" />
|
||||||
<NumberField source="media_count" />
|
<NumberField source="media_length" />
|
||||||
<NumberField source="media_length" />
|
</Datagrid>
|
||||||
</Datagrid>
|
</List>
|
||||||
</List>
|
);
|
||||||
);
|
|
||||||
|
const resource = {
|
||||||
|
name: "user_media_statistics",
|
||||||
|
icon: EqualizerIcon,
|
||||||
|
list: UserMediaStatsList,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default resource;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React, { cloneElement, Fragment } from "react";
|
import React, { cloneElement } from "react";
|
||||||
import Avatar from "@material-ui/core/Avatar";
|
import AssignmentIndIcon from "@mui/icons-material/AssignmentInd";
|
||||||
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
|
import ContactMailIcon from "@mui/icons-material/ContactMail";
|
||||||
import ContactMailIcon from "@material-ui/icons/ContactMail";
|
import DevicesIcon from "@mui/icons-material/Devices";
|
||||||
import DevicesIcon from "@material-ui/icons/Devices";
|
import GetAppIcon from "@mui/icons-material/GetApp";
|
||||||
import GetAppIcon from "@material-ui/icons/GetApp";
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
import NotificationsIcon from "@material-ui/icons/Notifications";
|
import PermMediaIcon from "@mui/icons-material/PermMedia";
|
||||||
import PermMediaIcon from "@material-ui/icons/PermMedia";
|
import PersonPinIcon from "@mui/icons-material/PersonPin";
|
||||||
import PersonPinIcon from "@material-ui/icons/PersonPin";
|
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent";
|
||||||
import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent";
|
import UserIcon from "@mui/icons-material/Group";
|
||||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
import ViewListIcon from "@mui/icons-material/ViewList";
|
||||||
import {
|
import {
|
||||||
ArrayInput,
|
ArrayInput,
|
||||||
ArrayField,
|
ArrayField,
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
Create,
|
Create,
|
||||||
Edit,
|
Edit,
|
||||||
List,
|
List,
|
||||||
Filter,
|
|
||||||
Toolbar,
|
Toolbar,
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
SimpleFormIterator,
|
SimpleFormIterator,
|
||||||
@ -49,28 +48,10 @@ import {
|
|||||||
NumberField,
|
NumberField,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import AvatarField from "./AvatarField";
|
||||||
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
import { ServerNoticeButton, ServerNoticeBulkButton } from "./ServerNotices";
|
||||||
import { DeviceRemoveButton } from "./devices";
|
import { DeviceRemoveButton } from "./devices";
|
||||||
import { ProtectMediaButton, QuarantineMediaButton } from "./media";
|
import { ProtectMediaButton, QuarantineMediaButton } from "./media";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
const redirect = () => {
|
|
||||||
return {
|
|
||||||
pathname: "/import_users",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
small: {
|
|
||||||
height: "40px",
|
|
||||||
width: "40px",
|
|
||||||
},
|
|
||||||
large: {
|
|
||||||
height: "120px",
|
|
||||||
width: "120px",
|
|
||||||
float: "right",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const choices_medium = [
|
const choices_medium = [
|
||||||
{ id: "email", name: "resources.users.email" },
|
{ id: "email", name: "resources.users.email" },
|
||||||
@ -92,7 +73,7 @@ const date_format = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UserListActions = ({
|
const UserListActions = ({
|
||||||
currentSort,
|
sort,
|
||||||
className,
|
className,
|
||||||
resource,
|
resource,
|
||||||
filters,
|
filters,
|
||||||
@ -101,7 +82,6 @@ const UserListActions = ({
|
|||||||
filterValues,
|
filterValues,
|
||||||
permanentFilter,
|
permanentFilter,
|
||||||
hasCreate, // you can hide CreateButton if hasCreate = false
|
hasCreate, // you can hide CreateButton if hasCreate = false
|
||||||
basePath,
|
|
||||||
selectedIds,
|
selectedIds,
|
||||||
onUnselectItems,
|
onUnselectItems,
|
||||||
showFilter,
|
showFilter,
|
||||||
@ -119,18 +99,18 @@ const UserListActions = ({
|
|||||||
filterValues,
|
filterValues,
|
||||||
context: "button",
|
context: "button",
|
||||||
})}
|
})}
|
||||||
<CreateButton basePath={basePath} />
|
<CreateButton />
|
||||||
<ExportButton
|
<ExportButton
|
||||||
disabled={total === 0}
|
disabled={total === 0}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
sort={currentSort}
|
sort={sort}
|
||||||
filter={{ ...filterValues, ...permanentFilter }}
|
filter={{ ...filterValues, ...permanentFilter }}
|
||||||
exporter={exporter}
|
exporter={exporter}
|
||||||
maxResults={maxResults}
|
maxResults={maxResults}
|
||||||
/>
|
/>
|
||||||
{/* Add your custom actions */}
|
{/* Add your custom actions */}
|
||||||
<Button component={Link} to={redirect} label="CSV Import">
|
<Button component={Link} to="/import_users" label="CSV Import">
|
||||||
<GetAppIcon style={{ transform: "rotate(180deg)", fontSize: "20" }} />
|
<GetAppIcon sx={{ transform: "rotate(180deg)", fontSize: "20px" }} />
|
||||||
</Button>
|
</Button>
|
||||||
</TopToolbar>
|
</TopToolbar>
|
||||||
);
|
);
|
||||||
@ -141,73 +121,62 @@ UserListActions.defaultProps = {
|
|||||||
onUnselectItems: () => null,
|
onUnselectItems: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserPagination = props => (
|
const UserPagination = () => (
|
||||||
<Pagination {...props} 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>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AvatarField = ({ source, className, record = {} }) => (
|
export const UserList = props => (
|
||||||
<Avatar src={record[source]} className={className} />
|
<List
|
||||||
|
{...props}
|
||||||
|
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" />
|
||||||
|
<BooleanField source="erased" sortable={false} />
|
||||||
|
<DateField
|
||||||
|
source="creation_ts"
|
||||||
|
label="resources.users.fields.creation_ts_ms"
|
||||||
|
showTime
|
||||||
|
options={date_format}
|
||||||
|
/>
|
||||||
|
</Datagrid>
|
||||||
|
</List>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UserList = props => {
|
|
||||||
const classes = useStyles();
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
{...props}
|
|
||||||
filters={<UserFilter />}
|
|
||||||
filterDefaultValues={{ guests: true, deactivated: false }}
|
|
||||||
sort={{ field: "name", order: "ASC" }}
|
|
||||||
actions={<UserListActions maxResults={10000} />}
|
|
||||||
bulkActionButtons={<UserBulkActionButtons />}
|
|
||||||
pagination={<UserPagination />}
|
|
||||||
>
|
|
||||||
<Datagrid rowClick="edit">
|
|
||||||
<AvatarField
|
|
||||||
source="avatar_src"
|
|
||||||
className={classes.small}
|
|
||||||
sortBy="avatar_url"
|
|
||||||
/>
|
|
||||||
<TextField source="id" sortBy="name" />
|
|
||||||
<TextField source="displayname" />
|
|
||||||
<BooleanField source="is_guest" />
|
|
||||||
<BooleanField source="admin" />
|
|
||||||
<BooleanField source="deactivated" />
|
|
||||||
<BooleanField source="erased" sortable={false} />
|
|
||||||
<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
|
// https://matrix.org/docs/spec/appendices#user-identifiers
|
||||||
// here only local part of user_id
|
// here only local part of user_id
|
||||||
// maxLength = 255 - "@" - ":" - localStorage.getItem("home_server").length
|
// maxLength = 255 - "@" - ":" - localStorage.getItem("home_server").length
|
||||||
@ -263,7 +232,7 @@ export function generateRandomUser() {
|
|||||||
|
|
||||||
const UserEditToolbar = props => (
|
const UserEditToolbar = props => (
|
||||||
<Toolbar {...props}>
|
<Toolbar {...props}>
|
||||||
<SaveButton submitOnEnter={true} disabled={props.pristine} />
|
<SaveButton disabled={props.pristine} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -303,7 +272,6 @@ export const UserCreate = props => (
|
|||||||
source="user_type"
|
source="user_type"
|
||||||
choices={choices_type}
|
choices={choices_type}
|
||||||
translateChoice={false}
|
translateChoice={false}
|
||||||
allowEmpty={true}
|
|
||||||
resettable
|
resettable
|
||||||
/>
|
/>
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
@ -331,7 +299,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 (
|
||||||
@ -345,7 +313,6 @@ const UserTitle = props => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const UserEdit = props => {
|
export const UserEdit = props => {
|
||||||
const classes = useStyles();
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
return (
|
return (
|
||||||
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
<Edit {...props} title={<UserTitle />} actions={<UserEditActions />}>
|
||||||
@ -357,7 +324,7 @@ export const UserEdit = props => {
|
|||||||
<AvatarField
|
<AvatarField
|
||||||
source="avatar_src"
|
source="avatar_src"
|
||||||
sortable={false}
|
sortable={false}
|
||||||
className={classes.large}
|
sx={{ height: "120px", width: "120px", float: "right" }}
|
||||||
/>
|
/>
|
||||||
<TextInput source="id" disabled />
|
<TextInput source="id" disabled />
|
||||||
<TextInput source="displayname" />
|
<TextInput source="displayname" />
|
||||||
@ -370,7 +337,6 @@ export const UserEdit = props => {
|
|||||||
source="user_type"
|
source="user_type"
|
||||||
choices={choices_type}
|
choices={choices_type}
|
||||||
translateChoice={false}
|
translateChoice={false}
|
||||||
allowEmpty={true}
|
|
||||||
resettable
|
resettable
|
||||||
/>
|
/>
|
||||||
<BooleanInput source="admin" />
|
<BooleanInput source="admin" />
|
||||||
@ -515,7 +481,7 @@ export const UserEdit = props => {
|
|||||||
>
|
>
|
||||||
<Datagrid
|
<Datagrid
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
rowClick={(id, basePath, record) => "/rooms/" + id + "/show"}
|
rowClick={(id, resource, record) => "/rooms/" + id + "/show"}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
source="id"
|
source="id"
|
||||||
@ -561,3 +527,13 @@ export const UserEdit = props => {
|
|||||||
</Edit>
|
</Edit>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resource = {
|
||||||
|
name: "users",
|
||||||
|
icon: UserIcon,
|
||||||
|
list: UserList,
|
||||||
|
edit: UserEdit,
|
||||||
|
create: UserCreate,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resource;
|
||||||
|
@ -198,7 +198,7 @@ const en = {
|
|||||||
event_json: {
|
event_json: {
|
||||||
origin: "origin server",
|
origin: "origin server",
|
||||||
origin_server_ts: "time of send",
|
origin_server_ts: "time of send",
|
||||||
type: "event typ",
|
type: "event type",
|
||||||
content: {
|
content: {
|
||||||
msgtype: "content type",
|
msgtype: "content type",
|
||||||
body: "content",
|
body: "content",
|
||||||
@ -346,7 +346,7 @@ const en = {
|
|||||||
title:
|
title:
|
||||||
"Delete room from directory |||| Delete %{smart_count} rooms from directory",
|
"Delete room from directory |||| Delete %{smart_count} rooms from directory",
|
||||||
content:
|
content:
|
||||||
"Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory",
|
"Are you sure you want to remove this room from directory? |||| Are you sure you want to remove these %{smart_count} rooms from directory?",
|
||||||
erase: "Delete from room directory",
|
erase: "Delete from room directory",
|
||||||
create: "Publish in room directory",
|
create: "Publish in room directory",
|
||||||
send_success: "Room successfully published.",
|
send_success: "Room successfully published.",
|
||||||
|
377
src/i18n/fr.js
Normal file
377
src/i18n/fr.js
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
import frenchMessages from "ra-language-french";
|
||||||
|
|
||||||
|
const fr = {
|
||||||
|
...frenchMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
base_url: "URL du serveur d’accueil",
|
||||||
|
welcome: "Bienvenue sur Synapse-admin",
|
||||||
|
server_version: "Version du serveur Synapse",
|
||||||
|
username_error:
|
||||||
|
"Veuillez entrer un nom d'utilisateur complet : « @utilisateur:domaine »",
|
||||||
|
protocol_error: "L'URL doit commencer par « http:// » ou « https:// »",
|
||||||
|
url_error: "L'URL du serveur Matrix n'est pas valide",
|
||||||
|
sso_sign_in: "Se connecter avec l’authentification unique",
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
invalid_user_id:
|
||||||
|
"Partie locale d'un identifiant utilisateur Matrix sans le nom du serveur d’accueil.",
|
||||||
|
tabs: { sso: "Authentification unique" },
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
tabs: {
|
||||||
|
basic: "Informations de base",
|
||||||
|
members: "Membres",
|
||||||
|
detail: "Détails",
|
||||||
|
permission: "Permissions",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: { tabs: { basic: "Informations de base", detail: "Détails" } },
|
||||||
|
},
|
||||||
|
import_users: {
|
||||||
|
error: {
|
||||||
|
at_entry: "Pour l'entrée %{entry} : %{message}",
|
||||||
|
error: "Erreur",
|
||||||
|
required_field: "Le champ requis « %{field} » est manquant",
|
||||||
|
invalid_value:
|
||||||
|
"Valeur non valide à la ligne %{row}. Le champ « %{field} » ne peut être que « true » ou « false »",
|
||||||
|
unreasonably_big:
|
||||||
|
"Refus de charger un fichier trop volumineux de %{size} mégaoctets",
|
||||||
|
already_in_progress: "Un import est déjà en cours",
|
||||||
|
id_exits: "L'identifiant %{id} déjà présent",
|
||||||
|
},
|
||||||
|
title: "Importer des utilisateurs à partir d'un fichier CSV",
|
||||||
|
goToPdf: "Voir le PDF",
|
||||||
|
cards: {
|
||||||
|
importstats: {
|
||||||
|
header: "Importer des utilisateurs",
|
||||||
|
users_total:
|
||||||
|
"%{smart_count} utilisateur dans le fichier CSV |||| %{smart_count} utilisateurs dans le fichier CSV",
|
||||||
|
guest_count: "%{smart_count} visiteur |||| %{smart_count} visiteurs",
|
||||||
|
admin_count:
|
||||||
|
"%{smart_count} administrateur |||| %{smart_count} administrateurs",
|
||||||
|
},
|
||||||
|
conflicts: {
|
||||||
|
header: "Stratégie de résolution des conflits",
|
||||||
|
mode: {
|
||||||
|
stop: "S'arrêter en cas de conflit",
|
||||||
|
skip: "Afficher l'erreur et ignorer le conflit",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ids: {
|
||||||
|
header: "Identifiants",
|
||||||
|
all_ids_present: "Identifiants présents pour chaque entrée",
|
||||||
|
count_ids_present:
|
||||||
|
"%{smart_count} entrée avec identifiant |||| %{smart_count} entrées avec identifiant",
|
||||||
|
mode: {
|
||||||
|
ignore:
|
||||||
|
"Ignorer les identifiants dans le ficher CSV et en créer de nouveaux",
|
||||||
|
update: "Mettre à jour les enregistrements existants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
passwords: {
|
||||||
|
header: "Mots de passe",
|
||||||
|
all_passwords_present: "Mots de passe présents pour chaque entrée",
|
||||||
|
count_passwords_present:
|
||||||
|
"%{smart_count} entrée avec mot de passe |||| %{smart_count} entrées avec mot de passe",
|
||||||
|
use_passwords: "Utiliser les mots de passe provenant du fichier CSV",
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
header: "Fichier CSV en entrée",
|
||||||
|
explanation:
|
||||||
|
"Vous pouvez télécharger ici un fichier contenant des valeurs séparées par des virgules qui sera traité pour créer ou mettre à jour des utilisateurs. Le fichier doit inclure les champs « id » et « displayname ». Vous pouvez télécharger et adapter un fichier d'exemple ici : ",
|
||||||
|
},
|
||||||
|
startImport: {
|
||||||
|
simulate_only: "Simuler",
|
||||||
|
run_import: "Importer",
|
||||||
|
},
|
||||||
|
results: {
|
||||||
|
header: "Résultats de l'import",
|
||||||
|
total:
|
||||||
|
"%{smart_count} entrée au total |||| %{smart_count} entrées au total",
|
||||||
|
successful: "%{smart_count} entrées importées avec succès",
|
||||||
|
skipped: "%{smart_count} entrées ignorées",
|
||||||
|
download_skipped: "Télécharger les entrées ignorées",
|
||||||
|
with_error:
|
||||||
|
"%{smart_count} entrée avec des erreurs ||| %{smart_count} entrées avec des erreurs",
|
||||||
|
simulated_only: "L'import était simulé",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
users: {
|
||||||
|
name: "Utilisateur |||| Utilisateurs",
|
||||||
|
email: "Adresse électronique",
|
||||||
|
msisdn: "Numéro de téléphone",
|
||||||
|
threepid: "Adresse électronique / Numéro de téléphone",
|
||||||
|
fields: {
|
||||||
|
avatar: "Avatar",
|
||||||
|
id: "Identifiant",
|
||||||
|
name: "Nom",
|
||||||
|
is_guest: "Visiteur",
|
||||||
|
admin: "Administrateur du serveur",
|
||||||
|
deactivated: "Désactivé",
|
||||||
|
guests: "Afficher les visiteurs",
|
||||||
|
show_deactivated: "Afficher les utilisateurs désactivés",
|
||||||
|
user_id: "Rechercher un utilisateur",
|
||||||
|
displayname: "Nom d'affichage",
|
||||||
|
password: "Mot de passe",
|
||||||
|
avatar_url: "URL de l'avatar",
|
||||||
|
avatar_src: "Avatar",
|
||||||
|
medium: "Type",
|
||||||
|
threepids: "Identifiants tiers",
|
||||||
|
address: "Adresse",
|
||||||
|
creation_ts_ms: "Date de création",
|
||||||
|
consent_version: "Version du consentement",
|
||||||
|
auth_provider: "Fournisseur d'identité",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
deactivate:
|
||||||
|
"Vous devrez fournir un mot de passe pour réactiver le compte.",
|
||||||
|
erase: "Marquer l'utilisateur comme effacé conformément au RGPD",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: "Effacer les données de l'utilisateur",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
name: "Salon |||| Salons",
|
||||||
|
fields: {
|
||||||
|
room_id: "Identifiant du salon",
|
||||||
|
name: "Nom",
|
||||||
|
canonical_alias: "Alias",
|
||||||
|
joined_members: "Membres",
|
||||||
|
joined_local_members: "Membres locaux",
|
||||||
|
joined_local_devices: "Appareils locaux",
|
||||||
|
state_events: "Événements d'État / Complexité",
|
||||||
|
version: "Version",
|
||||||
|
is_encrypted: "Chiffré",
|
||||||
|
encryption: "Chiffrement",
|
||||||
|
federatable: "Fédérable",
|
||||||
|
public: "Visible dans le répertoire des salons",
|
||||||
|
creator: "Créateur",
|
||||||
|
join_rules: "Règles d'adhésion",
|
||||||
|
guest_access: "Accès des visiteurs",
|
||||||
|
history_visibility: "Visibilité de l'historique",
|
||||||
|
topic: "Sujet",
|
||||||
|
avatar: "Avatar",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
forward_extremities:
|
||||||
|
"Les extrémités avant sont les événements feuilles à la fin d'un graphe orienté acyclique (DAG) dans un salon, c'est-à-dire les événements qui n'ont pas de descendants. Plus il y en a dans un salon, plus la résolution d'état que Synapse doit effectuer est importante (indice : c'est une opération coûteuse). Bien que Synapse dispose d'un algorithme pour éviter qu'un trop grand nombre de ces événements n'existent en même temps dans un salon, des bogues peuvent parfois les faire réapparaître. Si un salon présente plus de 10 extrémités avant, cela vaut la peine d'y prêter attention et éventuellement de les supprimer en utilisant les requêtes SQL mentionnées dans la discussion traitant du problème https://github.com/matrix-org/synapse/issues/1760.",
|
||||||
|
},
|
||||||
|
enums: {
|
||||||
|
join_rules: {
|
||||||
|
public: "Public",
|
||||||
|
knock: "Sur demande",
|
||||||
|
invite: "Sur invitation",
|
||||||
|
private: "Privé",
|
||||||
|
},
|
||||||
|
guest_access: {
|
||||||
|
can_join: "Les visiteurs peuvent rejoindre le salon",
|
||||||
|
forbidden: "Les visiteurs ne peuvent pas rejoindre le salon",
|
||||||
|
},
|
||||||
|
history_visibility: {
|
||||||
|
invited: "Depuis l'invitation",
|
||||||
|
joined: "Depuis l'adhésion",
|
||||||
|
shared: "Depuis le partage",
|
||||||
|
world_readable: "Tout le monde",
|
||||||
|
},
|
||||||
|
unencrypted: "Non chiffré",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Supprimer le salon",
|
||||||
|
content:
|
||||||
|
"Voulez-vous vraiment supprimer le salon ? Cette opération ne peut être annulée. Tous les messages et médias partagés du salon seront supprimés du serveur !",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
name: "Événement signalé |||| Événements signalés",
|
||||||
|
fields: {
|
||||||
|
id: "Identifiant",
|
||||||
|
received_ts: "Date du rapport",
|
||||||
|
user_id: "Rapporteur",
|
||||||
|
name: "Nom du salon",
|
||||||
|
score: "Score",
|
||||||
|
reason: "Motif",
|
||||||
|
event_id: "Identifiant de l'événement",
|
||||||
|
event_json: {
|
||||||
|
origin: "Serveur d'origine",
|
||||||
|
origin_server_ts: "Date d'envoi",
|
||||||
|
type: "Type d'événement",
|
||||||
|
content: {
|
||||||
|
msgtype: "Type de contenu",
|
||||||
|
body: "Contenu",
|
||||||
|
format: "Format",
|
||||||
|
formatted_body: "Contenu mis en forme",
|
||||||
|
algorithm: "Algorithme",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
name: "Connexions",
|
||||||
|
fields: {
|
||||||
|
last_seen: "Date",
|
||||||
|
ip: "Adresse IP",
|
||||||
|
user_agent: "Agent utilisateur",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
name: "Appareil |||| Appareils",
|
||||||
|
fields: {
|
||||||
|
device_id: "Identifiant de l'appareil",
|
||||||
|
display_name: "Nom de l'appareil",
|
||||||
|
last_seen_ts: "Date",
|
||||||
|
last_seen_ip: "Adresse IP",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Suppression de %{id}",
|
||||||
|
content: "Voulez-vous vraiment supprimer l'appareil « %{name} » ?",
|
||||||
|
success: "Appareil supprimé avec succès",
|
||||||
|
failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users_media: {
|
||||||
|
name: "Media",
|
||||||
|
fields: {
|
||||||
|
media_id: "Identifiant du média",
|
||||||
|
media_length: "Taille du fichier (en octets)",
|
||||||
|
media_type: "Type",
|
||||||
|
upload_name: "Nom du fichier",
|
||||||
|
quarantined_by: "Mis en quarantaine par",
|
||||||
|
safe_from_quarantine: "Protection contre la mise en quarantaine",
|
||||||
|
created_ts: "Date de création",
|
||||||
|
last_access_ts: "Dernier accès",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete_media: {
|
||||||
|
name: "Media",
|
||||||
|
fields: {
|
||||||
|
before_ts: "Dernier accès avant",
|
||||||
|
size_gt: "Plus grand que (en octets)",
|
||||||
|
keep_profiles: "Conserver les images de profil",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "Supprimer le média",
|
||||||
|
send_success: "Requête envoyée avec succès",
|
||||||
|
send_failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: "Cette API supprime les médias locaux du disque de votre propre serveur. Cela inclut toutes les vignettes locales et les copies des médias téléchargés. Cette API n'affectera pas les médias qui ont été téléversés dans des dépôts de médias externes.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
protect_media: {
|
||||||
|
action: {
|
||||||
|
create: "Protéger",
|
||||||
|
delete: "Révoquer la protection",
|
||||||
|
none: "En quarantaine",
|
||||||
|
send_success: "Le statut de protection a été modifié avec succès",
|
||||||
|
send_failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quarantine_media: {
|
||||||
|
action: {
|
||||||
|
name: "Quarantaine",
|
||||||
|
create: "Mettre en quarantaine",
|
||||||
|
delete: "Révoquer la mise en quarantaine",
|
||||||
|
none: "Protégé contre la mise en quarantaine",
|
||||||
|
send_success: "Le statut de la quarantaine a été modifié avec succès",
|
||||||
|
send_failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushers: {
|
||||||
|
name: "Émetteur de notifications |||| Émetteurs de notifications",
|
||||||
|
fields: {
|
||||||
|
app: "Application",
|
||||||
|
app_display_name: "Nom d'affichage de l'application",
|
||||||
|
app_id: "Identifiant de l'application",
|
||||||
|
device_display_name: "Nom d'affichage de l'appareil",
|
||||||
|
kind: "Type",
|
||||||
|
lang: "Langue",
|
||||||
|
profile_tag: "Profil",
|
||||||
|
pushkey: "Identifiant de l'émetteur",
|
||||||
|
data: { url: "URL" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servernotices: {
|
||||||
|
name: "Annonces du serveur",
|
||||||
|
send: "Envoyer des « Annonces du serveur »",
|
||||||
|
fields: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "Envoyer une annonce",
|
||||||
|
send_success: "Annonce envoyée avec succès",
|
||||||
|
send_failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: "Envoie une annonce au nom du serveur aux utilisateurs sélectionnés. La fonction « Annonces du serveur » doit être activée sur le serveur.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_media_statistics: {
|
||||||
|
name: "Médias des utilisateurs",
|
||||||
|
fields: {
|
||||||
|
media_count: "Nombre de médias",
|
||||||
|
media_length: "Taille des médias",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Extrémités avant",
|
||||||
|
fields: {
|
||||||
|
id: "Identifiant de l'événement",
|
||||||
|
received_ts: "Date de réception",
|
||||||
|
depth: "Profondeur",
|
||||||
|
state_group: "Groupe d'état",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_state: {
|
||||||
|
name: "Événements d'état",
|
||||||
|
fields: {
|
||||||
|
type: "Type",
|
||||||
|
content: "Contenu",
|
||||||
|
origin_server_ts: "Date d'envoi",
|
||||||
|
sender: "Expéditeur",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_directory: {
|
||||||
|
name: "Répertoire des salons",
|
||||||
|
fields: {
|
||||||
|
world_readable:
|
||||||
|
"Tout utilisateur peut avoir un aperçu du salon, sans en devenir membre",
|
||||||
|
guest_can_join: "Les visiteurs peuvent rejoindre le salon",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
title:
|
||||||
|
"Supprimer un salon du répertoire |||| Supprimer %{smart_count} salons du répertoire",
|
||||||
|
content:
|
||||||
|
"Voulez-vous vraiment supprimer ce salon du répertoire ? |||| Voulez-vous vraiment supprimer ces %{smart_count} salons du répertoire ?",
|
||||||
|
erase: "Supprimer du répertoire des salons",
|
||||||
|
create: "Publier dans le répertoire des salons",
|
||||||
|
send_success: "Salon publié avec succès",
|
||||||
|
send_failure: "Une erreur s'est produite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registration_tokens: {
|
||||||
|
name: "Jetons d'inscription",
|
||||||
|
fields: {
|
||||||
|
token: "Jeton",
|
||||||
|
valid: "Jeton valide",
|
||||||
|
uses_allowed: "Nombre d'inscription autorisées",
|
||||||
|
pending: "Nombre d'inscription en cours",
|
||||||
|
completed: "Nombre d'inscription accomplie",
|
||||||
|
expiry_time: "Date d'expiration",
|
||||||
|
length: "Longueur",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
length:
|
||||||
|
"Longueur du jeton généré aléatoirement si aucun jeton n'est pas spécifié",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default fr;
|
385
src/i18n/it.js
Normal file
385
src/i18n/it.js
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import italianMessages from "ra-language-italian";
|
||||||
|
|
||||||
|
const it = {
|
||||||
|
...italianMessages,
|
||||||
|
synapseadmin: {
|
||||||
|
auth: {
|
||||||
|
base_url: "URL dell'homeserver",
|
||||||
|
welcome: "Benvenuto in Synapse-admin",
|
||||||
|
server_version: "Versione di Synapse",
|
||||||
|
username_error:
|
||||||
|
"Per favore inserisci un ID utente completo: '@utente:dominio'",
|
||||||
|
protocol_error: "L'URL deve iniziare per 'http://' o 'https://'",
|
||||||
|
url_error: "URL del server Matrix non valido",
|
||||||
|
sso_sign_in: "Accedi con SSO",
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
invalid_user_id: "ID utente non valido su questo homeserver.",
|
||||||
|
tabs: { sso: "SSO" },
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
tabs: {
|
||||||
|
basic: "Semplice",
|
||||||
|
members: "Membro",
|
||||||
|
detail: "Dettagli",
|
||||||
|
permission: "Permessi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: { tabs: { basic: "Semplice", detail: "Dettagli" } },
|
||||||
|
},
|
||||||
|
import_users: {
|
||||||
|
error: {
|
||||||
|
at_entry: "Alla voce %{entry}: %{message}",
|
||||||
|
error: "Errore",
|
||||||
|
required_field: "Il campo '%{field}' non è presente",
|
||||||
|
invalid_value:
|
||||||
|
"Valore non valido alla riga %{row}. '%{field}' Il campo può essere solo 'true' o 'false'",
|
||||||
|
unreasonably_big:
|
||||||
|
"Impossibile caricare un file così grosso (%{size} megabyte)",
|
||||||
|
already_in_progress: "Un import è attualmente già in caricamento",
|
||||||
|
id_exits: "L'ID %{id} è già presente",
|
||||||
|
},
|
||||||
|
title: "Importa utenti tramite file CSV",
|
||||||
|
goToPdf: "Vai al PDF",
|
||||||
|
cards: {
|
||||||
|
importstats: {
|
||||||
|
header: "Importa utenti",
|
||||||
|
users_total:
|
||||||
|
"%{smart_count} utente nel file CSV |||| %{smart_count} utenti nel file CSV",
|
||||||
|
guest_count: "%{smart_count} ospite |||| %{smart_count} ospiti",
|
||||||
|
admin_count:
|
||||||
|
"%{smart_count} amministratore |||| %{smart_count} amministratori",
|
||||||
|
},
|
||||||
|
conflicts: {
|
||||||
|
header: "Strategia di conflitto",
|
||||||
|
mode: {
|
||||||
|
stop: "Stoppa al conflitto",
|
||||||
|
skip: "Mostra l'errore e ignora il conflitto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ids: {
|
||||||
|
header: "ID",
|
||||||
|
all_ids_present: "ID presenti in ogni voce",
|
||||||
|
count_ids_present:
|
||||||
|
"%{smart_count} voce con ID |||| %{smart_count} voci con ID",
|
||||||
|
mode: {
|
||||||
|
ignore: "Ignora gli ID nel file CSV e creane di nuovi",
|
||||||
|
update: "Aggiorna le voci esistenti",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
passwords: {
|
||||||
|
header: "Passwords",
|
||||||
|
all_passwords_present: "Password presenti in ogni voce",
|
||||||
|
count_passwords_present:
|
||||||
|
"%{smart_count} voce con password |||| %{smart_count} voci con password",
|
||||||
|
use_passwords: "Usa le password dal file CSV",
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
header: "Input file CSV",
|
||||||
|
explanation:
|
||||||
|
"Qui puoi caricare un file con valori separati da virgole che verrà poi utilizzato per creare o aggiornare gli utenti. Il file deve includere i campi 'id' and 'displayname'. Puoi scaricare un file di esempio per adattarlo: ",
|
||||||
|
},
|
||||||
|
startImport: {
|
||||||
|
simulate_only: "Solo simulazione",
|
||||||
|
run_import: "Importa",
|
||||||
|
},
|
||||||
|
results: {
|
||||||
|
header: "Importa i risultati",
|
||||||
|
total:
|
||||||
|
"%{smart_count} voce in totale |||| %{smart_count} voci in totale",
|
||||||
|
successful: "%{smart_count} voci importate con successo",
|
||||||
|
skipped: "%{smart_count} voci ignorate",
|
||||||
|
download_skipped: "Scarica le voci ignorate",
|
||||||
|
with_error:
|
||||||
|
"%{smart_count} voce con errori ||| %{smart_count} voci con errori",
|
||||||
|
simulated_only: "Il processo era stato solamente simulato",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
users: {
|
||||||
|
name: "Utente |||| Utenti",
|
||||||
|
email: "Email",
|
||||||
|
msisdn: "Telefono",
|
||||||
|
threepid: "Email / Telefono",
|
||||||
|
fields: {
|
||||||
|
avatar: "Avatar",
|
||||||
|
id: "ID utente",
|
||||||
|
name: "Nome",
|
||||||
|
is_guest: "Ospite",
|
||||||
|
admin: "Amministratore",
|
||||||
|
deactivated: "Disattivato",
|
||||||
|
guests: "Mostra gli ospiti",
|
||||||
|
show_deactivated: "Mostra gli utenti disattivati",
|
||||||
|
user_id: "Cerca utente",
|
||||||
|
displayname: "Nickname",
|
||||||
|
password: "Password",
|
||||||
|
avatar_url: "URL dell'avatar",
|
||||||
|
avatar_src: "Avatar",
|
||||||
|
medium: "Medium",
|
||||||
|
threepids: "3PID",
|
||||||
|
address: "Indirizzo",
|
||||||
|
creation_ts_ms: "Creazione del timestamp",
|
||||||
|
consent_version: "Versione minima richiesta",
|
||||||
|
auth_provider: "Provider",
|
||||||
|
user_type: "Tipo d'utente",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
password:
|
||||||
|
"Cambiando la password l'utente verrà disconnesso da tutte le sessioni attive.",
|
||||||
|
deactivate: "Devi fornire una password per riattivare l'account.",
|
||||||
|
erase: "Constrassegna l'utente come cancellato dal GDPR",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: "Cancella i dati dell'utente",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
name: "Stanza |||| Stanze",
|
||||||
|
fields: {
|
||||||
|
room_id: "ID della stanza",
|
||||||
|
name: "Nome",
|
||||||
|
canonical_alias: "Alias",
|
||||||
|
joined_members: "Membri",
|
||||||
|
joined_local_members: "Membri locali",
|
||||||
|
joined_local_devices: "Dispositivi locali",
|
||||||
|
state_events: "Eventi di stato / Complessità",
|
||||||
|
version: "Versione",
|
||||||
|
is_encrypted: "Criptato",
|
||||||
|
encryption: "Crittografia",
|
||||||
|
federatable: "Federabile",
|
||||||
|
public: "Visibile nella cartella della stanza",
|
||||||
|
creator: "Creatore",
|
||||||
|
join_rules: "Regole per entrare",
|
||||||
|
guest_access: "Entra come ospite",
|
||||||
|
history_visibility: "Visibilità temporale",
|
||||||
|
topic: "Topic",
|
||||||
|
avatar: "Avatar",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
/* forward_extremities:
|
||||||
|
"Forward extremities are the leaf events at the end of a Directed acyclic graph (DAG) in a room, aka events that have no children. The more exist in a room, the more state resolution that Synapse needs to perform (hint: it's an expensive operation). While Synapse has code to prevent too many of these existing at one time in a room, bugs can sometimes make them crop up again. If a room has >10 forward extremities, it's worth checking which room is the culprit and potentially removing them using the SQL queries mentioned in #1760.", */
|
||||||
|
},
|
||||||
|
enums: {
|
||||||
|
join_rules: {
|
||||||
|
public: "Pubblica",
|
||||||
|
knock: "Bussa",
|
||||||
|
invite: "Invita",
|
||||||
|
private: "Privata",
|
||||||
|
},
|
||||||
|
guest_access: {
|
||||||
|
can_join: "Gli utenti ospiti possono entrare",
|
||||||
|
forbidden: "Gli utenti ospiti non possono entrare",
|
||||||
|
},
|
||||||
|
history_visibility: {
|
||||||
|
invited: "Dall'invito",
|
||||||
|
joined: "Dall'entrata",
|
||||||
|
shared: "Dalla condivisione",
|
||||||
|
world_readable: "Chiunque",
|
||||||
|
},
|
||||||
|
unencrypted: "Non criptata",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Cancella stanza",
|
||||||
|
content:
|
||||||
|
"Sei sicuro di voler eliminare questa stanza? Questa azione è definitiva. Tutti i messaggi e i media condivisi in questa stanza verranno eliminati dal server!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
name: "Evento segnalato |||| Eventi segnalati",
|
||||||
|
fields: {
|
||||||
|
id: "ID",
|
||||||
|
received_ts: "Orario del report",
|
||||||
|
user_id: "richiedente",
|
||||||
|
name: "nome della stanza",
|
||||||
|
score: "punteggio",
|
||||||
|
reason: "ragione",
|
||||||
|
event_id: "ID dell'evento",
|
||||||
|
event_json: {
|
||||||
|
origin: "server di origine",
|
||||||
|
origin_server_ts: "ora dell'invio",
|
||||||
|
type: "tipo di evento",
|
||||||
|
content: {
|
||||||
|
msgtype: "tipo di contenuto",
|
||||||
|
body: "contenuto",
|
||||||
|
format: "formato",
|
||||||
|
formatted_body: "contenuto formattato",
|
||||||
|
algorithm: "algoritmo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: {
|
||||||
|
name: "Connessioni",
|
||||||
|
fields: {
|
||||||
|
last_seen: "Data",
|
||||||
|
ip: "Indirizzo IP",
|
||||||
|
user_agent: "agente utente",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
name: "Dispositivo |||| Dispositivi",
|
||||||
|
fields: {
|
||||||
|
device_id: "ID del dispositivo",
|
||||||
|
display_name: "Nome del dispositivo",
|
||||||
|
last_seen_ts: "Timestamp",
|
||||||
|
last_seen_ip: "Indirizzo IP",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Rimozione del dispositivo %{id}",
|
||||||
|
content: 'Sei sicuro di voler rimuovere il dispositivo "%{name}"?',
|
||||||
|
success: "Dispositivo rimosso con successo.",
|
||||||
|
failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
users_media: {
|
||||||
|
name: "Media",
|
||||||
|
fields: {
|
||||||
|
media_id: "ID del media",
|
||||||
|
media_length: "Peso del file (in Byte)",
|
||||||
|
media_type: "Tipo",
|
||||||
|
upload_name: "Nome del file",
|
||||||
|
quarantined_by: "In quarantena da",
|
||||||
|
safe_from_quarantine: "Protetto dalla quarantena",
|
||||||
|
created_ts: "Creato",
|
||||||
|
last_access_ts: "Ultimo accesso",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete_media: {
|
||||||
|
name: "Media",
|
||||||
|
fields: {
|
||||||
|
before_ts: "ultimo accesso effettuato prima",
|
||||||
|
size_gt: "Più grande di (in byte)",
|
||||||
|
keep_profiles: "Mantieni le immagini del profilo",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "Cancella media",
|
||||||
|
send_success: "Richiesta inviata con successo.",
|
||||||
|
send_failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: "Questa API cancella i media locali dal disco del tuo server. Questo include anche ogni miniatura e copia del media scaricato. Questa API non inciderà sui media che sono stati caricati nei repository esterni.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
protect_media: {
|
||||||
|
action: {
|
||||||
|
create: "Non protetto, proteggi",
|
||||||
|
delete: "Protetto, rimuovi protezione",
|
||||||
|
none: "In quarantena",
|
||||||
|
send_success: "Stato della protezione cambiato con successo.",
|
||||||
|
send_failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quarantine_media: {
|
||||||
|
action: {
|
||||||
|
name: "Quarantina",
|
||||||
|
create: "Aggiungi alla quarantena",
|
||||||
|
delete: "In quarantena, rimuovi dalla quarantena",
|
||||||
|
none: "Protetto dalla quarantena",
|
||||||
|
send_success: "Stato della quarantena cambiato con successo.",
|
||||||
|
send_failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushers: {
|
||||||
|
name: "Pusher |||| Pusher",
|
||||||
|
fields: {
|
||||||
|
app: "App",
|
||||||
|
app_display_name: "Nome dell'app",
|
||||||
|
app_id: "ID dell'app",
|
||||||
|
device_display_name: "Nome del dispositivo",
|
||||||
|
kind: "Tipo",
|
||||||
|
lang: "Lingua",
|
||||||
|
profile_tag: "Tag del profilo",
|
||||||
|
pushkey: "Pushkey",
|
||||||
|
data: { url: "URL" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servernotices: {
|
||||||
|
name: "Avvisi del server",
|
||||||
|
send: "Invia avvisi",
|
||||||
|
fields: {
|
||||||
|
body: "Messaggio",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "Invia nota",
|
||||||
|
send_success: "Avviso inviato con successo.",
|
||||||
|
send_failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: 'Invia un avviso dal server agli utenti selezionati. La feature "Avvisi del server" è stata attivata sul server.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_media_statistics: {
|
||||||
|
name: "Media degli utenti",
|
||||||
|
fields: {
|
||||||
|
media_count: "Numero media",
|
||||||
|
media_length: "Lunghezza media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Invia estremità",
|
||||||
|
fields: {
|
||||||
|
id: "Event ID",
|
||||||
|
received_ts: "Timestamp",
|
||||||
|
depth: "Profondità",
|
||||||
|
state_group: "State group",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_state: {
|
||||||
|
name: "Eventi di stato",
|
||||||
|
fields: {
|
||||||
|
type: "Tipo",
|
||||||
|
content: "Contenuto",
|
||||||
|
origin_server_ts: "Ora dell'invio",
|
||||||
|
sender: "Mittente",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_directory: {
|
||||||
|
name: "Elenco delle stanze",
|
||||||
|
fields: {
|
||||||
|
world_readable: "gli utenti ospite possono vedere senza entrare",
|
||||||
|
guest_can_join: "gli utenti ospite possono entrare",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
title:
|
||||||
|
"Cancella stanza dall'elenco |||| Cancella %{smart_count} stanze dall'elenco",
|
||||||
|
content:
|
||||||
|
"Sei sicuro di voler rimuovere questa stanza dall'elenco? |||| Sei sicuro di voler rimuovere %{smart_count} stanze dall'elenco?",
|
||||||
|
erase: "Rimuovi dall'elenco",
|
||||||
|
create: "Crea",
|
||||||
|
send_success: "Stanza creata con successo.",
|
||||||
|
send_failure: "C'è stato un errore.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destinations: {
|
||||||
|
name: "Federazione",
|
||||||
|
fields: {
|
||||||
|
destination: "Destinazione",
|
||||||
|
failure_ts: "Timestamp dell'errore",
|
||||||
|
retry_last_ts: "Tentativo ultimo timestamp",
|
||||||
|
retry_interval: "Intervallo dei tentativi",
|
||||||
|
last_successful_stream_ordering: "Ultimo flusso riuscito con successo",
|
||||||
|
stream_ordering: "Flusso",
|
||||||
|
},
|
||||||
|
action: { reconnect: "Riconnetti" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registration_tokens: {
|
||||||
|
name: "Token di registrazione",
|
||||||
|
fields: {
|
||||||
|
token: "Token",
|
||||||
|
valid: "Token valido",
|
||||||
|
uses_allowed: "Usi permessi",
|
||||||
|
pending: "In attesa",
|
||||||
|
completed: "Completato",
|
||||||
|
expiry_time: "Data della scadenza",
|
||||||
|
length: "Lunghezza",
|
||||||
|
},
|
||||||
|
helper: { length: "Lunghezza del token se non viene dato alcun token." },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default it;
|
@ -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.previousData.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",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -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.data, filterNullValues),
|
body: JSON.stringify(params.previousData, filterNullValues),
|
||||||
}).then(({ json }) => ({
|
}).then(({ json }) => ({
|
||||||
data: json,
|
data: json,
|
||||||
}));
|
}));
|
||||||
|
Loading…
Reference in New Issue
Block a user