Compare commits
4 Commits
v0.10.3-et
...
v0.10.3-et
Author | SHA1 | Date | |
---|---|---|---|
![]() |
02bee92806 | ||
![]() |
b7f8e03894 | ||
![]() |
59bb4b4183 | ||
![]() |
bb53d53692 |
268
README.md
268
README.md
@@ -1,7 +1,32 @@
|
|||||||
# Synapse Admin UI [](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
|
# Synapse Admin UI [](https://github.com/Awesome-Technologies/synapse-admin/blob/master/LICENSE)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
This project is built using [react-admin](https://marmelab.com/react-admin/).
|
||||||
|
|
||||||
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
|
* [Fork differences](#fork-differences)
|
||||||
|
* [Available via CDN](#available-via-cdn)
|
||||||
|
* [Changes](#changes)
|
||||||
|
* [Development](#development)
|
||||||
|
* [Configuration](#configuration)
|
||||||
|
* [Restricting available homeserver](#restricting-available-homeserver)
|
||||||
|
* [Protecting appservice managed users](#protecting-appservice-managed-users)
|
||||||
|
* [Providing support URL](#providing-support-url)
|
||||||
|
* [Usage](#usage)
|
||||||
|
* [Supported Synapse](#supported-synapse)
|
||||||
|
* [Prerequisites](#prerequisites)
|
||||||
|
* [Use without install](#use-without-install)
|
||||||
|
* [Step-By-Step install](#step-by-step-install)
|
||||||
|
* [Steps for 1)](#steps-for-1)
|
||||||
|
* [Steps for 2)](#steps-for-2)
|
||||||
|
* [Steps for 3)](#steps-for-3)
|
||||||
|
* [Serving Synapse-Admin on a different path](#serving-synapse-admin-on-a-different-path)
|
||||||
|
* [Development](#development-1)
|
||||||
|
|
||||||
|
<!-- vim-markdown-toc -->
|
||||||
|
|
||||||
## Fork differences
|
## Fork differences
|
||||||
|
|
||||||
With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologies/synapse-admin) as the upstream, this
|
With [Awesome-Technologies/synapse-admin](https://github.com/Awesome-Technologies/synapse-admin) as the upstream, this
|
||||||
@@ -34,6 +59,7 @@ The following changes are already implemented:
|
|||||||
* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27)
|
* [Expose user avatar URL field in the UI](https://github.com/etkecc/synapse-admin/pull/27)
|
||||||
* [Upgrade react-admin to v5](https://github.com/etkecc/synapse-admin/pull/40)
|
* [Upgrade react-admin to v5](https://github.com/etkecc/synapse-admin/pull/40)
|
||||||
* [Restrict actions on specific users](https://github.com/etkecc/synapse-admin/pull/42)
|
* [Restrict actions on specific users](https://github.com/etkecc/synapse-admin/pull/42)
|
||||||
|
* [Add `Contact support` menu item](https://github.com/etkecc/synapse-admin/pull/45)
|
||||||
|
|
||||||
_the list will be updated as new changes are added_
|
_the list will be updated as new changes are added_
|
||||||
|
|
||||||
@@ -48,122 +74,6 @@ After that open `http://localhost:5173` in your browser, login using the followi
|
|||||||
* Password: admin
|
* Password: admin
|
||||||
* Homeserver URL: http://localhost:8008
|
* Homeserver URL: http://localhost:8008
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Supported Synapse
|
|
||||||
|
|
||||||
It needs at least [Synapse](https://github.com/element-hq/synapse) v1.93.0 for all functions to work as expected!
|
|
||||||
|
|
||||||
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
|
||||||
See also [Synapse version API](https://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.
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
You need access to the following endpoints:
|
|
||||||
|
|
||||||
- `/_matrix`
|
|
||||||
- `/_synapse/admin`
|
|
||||||
|
|
||||||
See also [Synapse administration endpoints](https://element-hq.github.io/synapse/latest/reverse_proxy.html#synapse-administration-endpoints)
|
|
||||||
|
|
||||||
### Use without install
|
|
||||||
|
|
||||||
You can use the current version of Synapse Admin without own installation direct
|
|
||||||
via [GitHub Pages](https://awesome-technologies.github.io/synapse-admin/).
|
|
||||||
|
|
||||||
**Note:**
|
|
||||||
If you want to use the deployment, you have to make sure that the admin endpoints (`/_synapse/admin`) are accessible for your browser.
|
|
||||||
**Remember: You have no need to expose these endpoints to the internet but to your network.**
|
|
||||||
If you want your own deployment, follow the [Step-By-Step Install Guide](#step-by-step-install) below.
|
|
||||||
|
|
||||||
### Step-By-Step install
|
|
||||||
|
|
||||||
You have three options:
|
|
||||||
|
|
||||||
1. [Download the tarball and serve with any webserver](#steps-for-1)
|
|
||||||
2. [Download the source code from github and run using nodejs](#steps-for-2)
|
|
||||||
3. [Run the Docker container](#steps-for-3)
|
|
||||||
|
|
||||||
#### Steps for 1)
|
|
||||||
|
|
||||||
- make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do)
|
|
||||||
- configure a vhost for synapse admin on your webserver
|
|
||||||
- download the .tar.gz from the latest release: https://github.com/Awesome-Technologies/synapse-admin/releases/latest
|
|
||||||
- unpack the .tar.gz
|
|
||||||
- move or symlink the `synapse-admin-x.x.x` into your vhosts root dir
|
|
||||||
- open the url of the vhost in your browser
|
|
||||||
|
|
||||||
#### Steps for 2)
|
|
||||||
|
|
||||||
- make sure you have installed the following: git, yarn, nodejs
|
|
||||||
- download the source code: `git clone https://github.com/Awesome-Technologies/synapse-admin.git`
|
|
||||||
- change into downloaded directory: `cd synapse-admin`
|
|
||||||
- download dependencies: `yarn install`
|
|
||||||
- start web server: `yarn start`
|
|
||||||
|
|
||||||
#### Steps for 3)
|
|
||||||
|
|
||||||
- run the Docker container from the public docker registry: `docker run -p 8080:80 awesometechnologies/synapse-admin` or use the [docker-compose.yml](docker-compose.yml): `docker-compose up -d`
|
|
||||||
|
|
||||||
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
synapse-admin:
|
|
||||||
container_name: synapse-admin
|
|
||||||
hostname: synapse-admin
|
|
||||||
build:
|
|
||||||
context: https://github.com/Awesome-Technologies/synapse-admin.git
|
|
||||||
args:
|
|
||||||
- BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
|
||||||
# - NODE_OPTIONS="--max_old_space_size=1024"
|
|
||||||
# - BASE_PATH="/synapse-admin"
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
- browse to http://localhost:8080
|
|
||||||
|
|
||||||
### Serving Synapse-Admin on a different path
|
|
||||||
|
|
||||||
The path prefix where synapse-admin is served can only be changed during the build step.
|
|
||||||
|
|
||||||
If you downloaded the source code, use `yarn build --base=/my-prefix` to set a path prefix.
|
|
||||||
|
|
||||||
If you want to build your own Docker container, use the `BASE_PATH` argument.
|
|
||||||
|
|
||||||
We do not support directly changing the path where Synapse-Admin is served in the pre-built Docker container. Instead please use a reverse proxy if you need to move Synapse-Admin to a different base path. If you want to serve multiple applications with different paths on the same domain, you need a reverse proxy anyway.
|
|
||||||
|
|
||||||
Example for Traefik:
|
|
||||||
|
|
||||||
`docker-compose.yml`
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: traefik:mimolette
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 443:443
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
|
|
||||||
synapse-admin:
|
|
||||||
image: awesometechnologies/synapse-admin:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.synapse-admin.rule=Host(`example.com`)&&PathPrefix(`/admin`)"
|
|
||||||
- "traefik.http.routers.synapse-admin.middlewares=admin,admin_path"
|
|
||||||
- "traefik.http.middlewares.admin.redirectregex.regex=^(.*)/admin/?"
|
|
||||||
- "traefik.http.middlewares.admin.redirectregex.replacement=$${1}/admin/"
|
|
||||||
- "traefik.http.middlewares.admin_path.stripprefix.prefixes=/admin"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
You can use `config.json` file to configure synapse-admin
|
You can use `config.json` file to configure synapse-admin
|
||||||
@@ -212,9 +122,131 @@ Example for [mautrix-telegram](https://github.com/mautrix/telegram)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Screenshots
|
### Providing support URL
|
||||||
|
|
||||||

|
Synapse-Admin provides a support link in the main menu - `Contact support`. By default, the link points to the GitHub issues page of the project. You can change this link by providing a `supportURL` in the `config.json`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"supportURL": "https://example.com/support"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Supported Synapse
|
||||||
|
|
||||||
|
It needs at least [Synapse](https://github.com/element-hq/synapse) v1.93.0 for all functions to work as expected!
|
||||||
|
|
||||||
|
You get your server version with the request `/_synapse/admin/v1/server_version`.
|
||||||
|
See also [Synapse version API](https://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.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
You need access to the following endpoints:
|
||||||
|
|
||||||
|
- `/_matrix`
|
||||||
|
- `/_synapse/admin`
|
||||||
|
|
||||||
|
See also [Synapse administration endpoints](https://element-hq.github.io/synapse/latest/reverse_proxy.html#synapse-administration-endpoints)
|
||||||
|
|
||||||
|
### Use without install
|
||||||
|
|
||||||
|
You can use the current version of Synapse Admin without own installation direct
|
||||||
|
via [admin.etke.cc](https://admin.etke.cc).
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
If you want to use the deployment, you have to make sure that the admin endpoints (`/_synapse/admin`) are accessible for your browser.
|
||||||
|
**Remember: You have no need to expose these endpoints to the internet but to your network.**
|
||||||
|
If you want your own deployment, follow the [Step-By-Step Install Guide](#step-by-step-install) below.
|
||||||
|
|
||||||
|
### Step-By-Step install
|
||||||
|
|
||||||
|
You have three options:
|
||||||
|
|
||||||
|
1. [Download the tarball and serve with any webserver](#steps-for-1)
|
||||||
|
2. [Download the source code from github and run using nodejs](#steps-for-2)
|
||||||
|
3. [Run the Docker container](#steps-for-3)
|
||||||
|
|
||||||
|
#### Steps for 1)
|
||||||
|
|
||||||
|
- make sure you have a webserver installed that can serve static files (any webserver like nginx or apache will do)
|
||||||
|
- configure a vhost for synapse admin on your webserver
|
||||||
|
- download the .tar.gz [from the latest release](https://github.com/etkecc/synapse-admin/releases/latest)
|
||||||
|
- unpack the .tar.gz
|
||||||
|
- move or symlink the `synapse-admin` into your vhosts root dir
|
||||||
|
- open the url of the vhost in your browser
|
||||||
|
|
||||||
|
#### Steps for 2)
|
||||||
|
|
||||||
|
- make sure you have installed the following: git, yarn, nodejs
|
||||||
|
- download the source code: `git clone https://github.com/etkecc/synapse-admin.git`
|
||||||
|
- change into downloaded directory: `cd synapse-admin`
|
||||||
|
- download dependencies: `yarn install`
|
||||||
|
- start web server: `yarn start`
|
||||||
|
|
||||||
|
#### Steps for 3)
|
||||||
|
|
||||||
|
- run the Docker container from the public docker registry: `docker run -p 8080:80 ghcr.io/etkecc/synapse-admin` or use the [docker-compose.yml](docker-compose.yml): `docker-compose up -d`
|
||||||
|
|
||||||
|
> note: if you're building on an architecture other than amd64 (for example a raspberry pi), make sure to define a maximum ram for node. otherwise the build will fail.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
synapse-admin:
|
||||||
|
container_name: synapse-admin
|
||||||
|
hostname: synapse-admin
|
||||||
|
build:
|
||||||
|
context: https://github.com/etkecc/synapse-admin.git
|
||||||
|
args:
|
||||||
|
- BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
|
# - NODE_OPTIONS="--max_old_space_size=1024"
|
||||||
|
# - BASE_PATH="/synapse-admin"
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
- browse to http://localhost:8080
|
||||||
|
|
||||||
|
### Serving Synapse-Admin on a different path
|
||||||
|
|
||||||
|
The path prefix where synapse-admin is served can only be changed during the build step.
|
||||||
|
|
||||||
|
If you downloaded the source code, use `yarn build --base=/my-prefix` to set a path prefix.
|
||||||
|
|
||||||
|
If you want to build your own Docker container, use the `BASE_PATH` argument.
|
||||||
|
|
||||||
|
We do not support directly changing the path where Synapse-Admin is served in the pre-built Docker container. Instead please use a reverse proxy if you need to move Synapse-Admin to a different base path. If you want to serve multiple applications with different paths on the same domain, you need a reverse proxy anyway.
|
||||||
|
|
||||||
|
Example for Traefik:
|
||||||
|
|
||||||
|
`docker-compose.yml`
|
||||||
|
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:mimolette
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
synapse-admin:
|
||||||
|
image: etkecc/synapse-admin:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.synapse-admin.rule=Host(`example.com`)&&PathPrefix(`/admin`)"
|
||||||
|
- "traefik.http.routers.synapse-admin.middlewares=admin,admin_path"
|
||||||
|
- "traefik.http.middlewares.admin.redirectregex.regex=^(.*)/admin/?"
|
||||||
|
- "traefik.http.middlewares.admin.redirectregex.replacement=$${1}/admin/"
|
||||||
|
- "traefik.http.middlewares.admin_path.stripprefix.prefixes=/admin"
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import polyglotI18nProvider from "ra-i18n-polyglot";
|
|||||||
import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
|
import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
|
||||||
import { Route } from "react-router-dom";
|
import { Route } from "react-router-dom";
|
||||||
|
|
||||||
|
import { AdminLayout } from "./components/AdminLayout";
|
||||||
import { ImportFeature } from "./components/ImportFeature";
|
import { ImportFeature } from "./components/ImportFeature";
|
||||||
import germanMessages from "./i18n/de";
|
import germanMessages from "./i18n/de";
|
||||||
import englishMessages from "./i18n/en";
|
import englishMessages from "./i18n/en";
|
||||||
@@ -21,6 +22,7 @@ import userMediaStats from "./resources/user_media_statistics";
|
|||||||
import users from "./resources/users";
|
import users from "./resources/users";
|
||||||
import authProvider from "./synapse/authProvider";
|
import authProvider from "./synapse/authProvider";
|
||||||
import dataProvider from "./synapse/dataProvider";
|
import dataProvider from "./synapse/dataProvider";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
|
||||||
// TODO: Can we use lazy loading together with browser locale?
|
// TODO: Can we use lazy loading together with browser locale?
|
||||||
const messages = {
|
const messages = {
|
||||||
@@ -45,10 +47,14 @@ const i18nProvider = polyglotI18nProvider(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<Admin
|
<Admin
|
||||||
disableTelemetry
|
disableTelemetry
|
||||||
requireAuth
|
requireAuth
|
||||||
|
layout={AdminLayout}
|
||||||
loginPage={LoginPage}
|
loginPage={LoginPage}
|
||||||
authProvider={authProvider}
|
authProvider={authProvider}
|
||||||
dataProvider={dataProvider}
|
dataProvider={dataProvider}
|
||||||
@@ -75,6 +81,7 @@ const App = () => (
|
|||||||
<Resource name="room_state" />
|
<Resource name="room_state" />
|
||||||
<Resource name="destination_rooms" />
|
<Resource name="destination_rooms" />
|
||||||
</Admin>
|
</Admin>
|
||||||
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@@ -3,6 +3,7 @@ import { createContext, useContext } from "react";
|
|||||||
interface AppContextType {
|
interface AppContextType {
|
||||||
restrictBaseUrl: string | string[];
|
restrictBaseUrl: string | string[];
|
||||||
asManagedUsers: string[];
|
asManagedUsers: string[];
|
||||||
|
supportURL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppContext = createContext({});
|
export const AppContext = createContext({});
|
||||||
|
26
src/components/AdminLayout.tsx
Normal file
26
src/components/AdminLayout.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Layout, Menu } from 'react-admin';
|
||||||
|
import LiveHelpIcon from '@mui/icons-material/LiveHelp';
|
||||||
|
|
||||||
|
const DEFAULT_SUPPORT_LINK = "https://github.com/etkecc/synapse-admin/issues";
|
||||||
|
const supportLink = (): string => {
|
||||||
|
try {
|
||||||
|
new URL(localStorage.getItem("support_url") || ''); // Check if the URL is valid
|
||||||
|
return localStorage.getItem("support_url") || DEFAULT_SUPPORT_LINK;
|
||||||
|
} catch (e) {
|
||||||
|
return DEFAULT_SUPPORT_LINK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const AdminMenu = () => (
|
||||||
|
<Menu>
|
||||||
|
<Menu.ResourceItems />
|
||||||
|
<Menu.Item to={supportLink()} target="_blank" primaryText="Contact support" leftIcon={<LiveHelpIcon />} />
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AdminLayout = ({ children }) => (
|
||||||
|
<Layout menu={AdminMenu}>
|
||||||
|
{children}
|
||||||
|
</Layout>
|
||||||
|
);
|
@@ -1,11 +1,12 @@
|
|||||||
|
import { Identifier } from "ra-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a user is managed by an application service
|
* Check if a user is managed by an application service
|
||||||
* @param id The user ID to check
|
* @param id The user ID to check
|
||||||
* @returns Whether the user is managed by an application service
|
* @returns Whether the user is managed by an application service
|
||||||
*/
|
*/
|
||||||
export const isASManaged = (id: string) => {
|
export const isASManaged = (id: string | Identifier): boolean => {
|
||||||
const managedUsersString = localStorage.getItem("as_managed_users");
|
const managedUsersString = localStorage.getItem("as_managed_users") || '';
|
||||||
try {
|
try {
|
||||||
const asManagedUsers = JSON.parse(managedUsersString).map(regex => new RegExp(regex));
|
const asManagedUsers = JSON.parse(managedUsersString).map(regex => new RegExp(regex));
|
||||||
return asManagedUsers.some(regex => regex.test(id));
|
return asManagedUsers.some(regex => regex.test(id));
|
||||||
|
1
src/i18n/index.d.ts
vendored
1
src/i18n/index.d.ts
vendored
@@ -139,6 +139,7 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
|||||||
deactivate: string;
|
deactivate: string;
|
||||||
erase: string;
|
erase: string;
|
||||||
erase_admin_error: string;
|
erase_admin_error: string;
|
||||||
|
modify_managed_user_error: string;
|
||||||
};
|
};
|
||||||
action: {
|
action: {
|
||||||
erase: string;
|
erase: string;
|
||||||
|
@@ -10,6 +10,7 @@ fetch("config.json")
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(props => {
|
.then(props => {
|
||||||
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
|
storage.setItem("as_managed_users", JSON.stringify(props.asManagedUsers));
|
||||||
|
storage.setItem("support_url", props.supportURL);
|
||||||
return createRoot(document.getElementById("root")).render(
|
return createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<AppContext.Provider value={props}>
|
<AppContext.Provider value={props}>
|
||||||
|
@@ -244,6 +244,10 @@ export const UserCreate = (props: CreateProps) => (
|
|||||||
|
|
||||||
const UserTitle = () => {
|
const UserTitle = () => {
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
let username = record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""
|
let username = record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""
|
||||||
if (isASManaged(record?.id)) {
|
if (isASManaged(record?.id)) {
|
||||||
@@ -360,7 +364,7 @@ export const UserEdit = (props: EditProps) => {
|
|||||||
|
|
||||||
<FormTab label={translate("resources.devices.name", { smart_count: 2 })} icon={<DevicesIcon />} path="devices">
|
<FormTab label={translate("resources.devices.name", { smart_count: 2 })} icon={<DevicesIcon />} path="devices">
|
||||||
<ReferenceManyField reference="devices" target="user_id" label={false}>
|
<ReferenceManyField reference="devices" target="user_id" label={false}>
|
||||||
<Datagrid style={{ width: "100%" }} bulkActionButtons="">
|
<Datagrid style={{ width: "100%" }} bulkActionButtons={false}>
|
||||||
<TextField source="device_id" sortable={false} />
|
<TextField source="device_id" sortable={false} />
|
||||||
<TextField source="display_name" sortable={false} />
|
<TextField source="display_name" sortable={false} />
|
||||||
<TextField source="last_seen_ip" sortable={false} />
|
<TextField source="last_seen_ip" sortable={false} />
|
||||||
|
Reference in New Issue
Block a user