parent
cfd8238edc
commit
20417a67b9
@ -99,6 +99,7 @@ The following changes are already implemented:
|
|||||||
* 🎨 [Add preferred theme colors to login page and footer](https://github.com/etkecc/synapse-admin/pull/155)
|
* 🎨 [Add preferred theme colors to login page and footer](https://github.com/etkecc/synapse-admin/pull/155)
|
||||||
* 🔰 [Add "Assign Admin" button to the rooms](https://github.com/etkecc/synapse-admin/pull/156)
|
* 🔰 [Add "Assign Admin" button to the rooms](https://github.com/etkecc/synapse-admin/pull/156)
|
||||||
* 🖼️ [Add rooms' avatars](https://github.com/etkecc/synapse-admin/pull/158)
|
* 🖼️ [Add rooms' avatars](https://github.com/etkecc/synapse-admin/pull/158)
|
||||||
|
* 🤖 [User Badges](https://github.com/etkecc/synapse-admin/pull/160)
|
||||||
|
|
||||||
_the list will be updated as new changes are added_
|
_the list will be updated as new changes are added_
|
||||||
|
|
||||||
|
36
docs/user-badges.md
Normal file
36
docs/user-badges.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# User Badges
|
||||||
|
|
||||||
|
To help with identifying users with certain roles or permissions, we have implemented a badge system.
|
||||||
|
These badges are displayed on the user's avatar and have a handy tooltip that explains what the badge means.
|
||||||
|
|
||||||
|
## Available Badges
|
||||||
|
|
||||||
|
### 🧙 You
|
||||||
|
|
||||||
|
This badge is displayed on your user's avatar.
|
||||||
|
Tooltip for this badge will contain additional information, e.g.: `You (Admin)`.
|
||||||
|
|
||||||
|
### 👑 Admin
|
||||||
|
|
||||||
|
This badge is displayed on homeserver admins' avatars.
|
||||||
|
Tooltip for this badge is `Admin`.
|
||||||
|
|
||||||
|
### 🛡️ Appservice/System-managed
|
||||||
|
|
||||||
|
This badge is displayed on users that are managed by an appservices (or system), [more details](./system-users.md).
|
||||||
|
Tooltip for this badge will contain additional information, e.g.: `System-managed (Bot)`.
|
||||||
|
|
||||||
|
### 🤖 Bot
|
||||||
|
|
||||||
|
This badge is displayed on bots' avatars (users with the `user_type` set to `bot`).
|
||||||
|
Tooltip for this badge is `Bot`.
|
||||||
|
|
||||||
|
### 📞 Support
|
||||||
|
|
||||||
|
This badge is displayed on users that are part of the support team (users with the `user_type` set to `support`).
|
||||||
|
Tooltip for this badge is `Support`.
|
||||||
|
|
||||||
|
### 👤 Regular User
|
||||||
|
|
||||||
|
This badge is displayed on regular users' avatars.
|
||||||
|
Tooltip for this badge is `Regular User`.
|
@ -1,8 +1,10 @@
|
|||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { Avatar, AvatarProps } from "@mui/material";
|
import { Avatar, AvatarProps, Badge, Tooltip } from "@mui/material";
|
||||||
import { FieldProps, useRecordContext } from "react-admin";
|
import { FieldProps, useRecordContext, useTranslate } from "react-admin";
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
import { fetchAuthenticatedMedia } from "../utils/fetchMedia";
|
||||||
|
import { isMXID, isASManaged } from "./mxid";
|
||||||
|
import storage from "../storage";
|
||||||
|
|
||||||
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
||||||
const { alt, classes, sizes, sx, variant } = rest;
|
const { alt, classes, sizes, sx, variant } = rest;
|
||||||
@ -45,6 +47,59 @@ const AvatarField = ({ source, ...rest }: AvatarProps & FieldProps) => {
|
|||||||
letter = record.displayname[0].toUpperCase();
|
letter = record.displayname[0].toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hacky way to determine the user type
|
||||||
|
let badge = "";
|
||||||
|
let tooltip = "";
|
||||||
|
if (isMXID(record?.id)) {
|
||||||
|
const translate = useTranslate();
|
||||||
|
switch (record?.user_type) {
|
||||||
|
case "bot":
|
||||||
|
badge = "🤖";
|
||||||
|
tooltip = translate("resources.users.badge.bot");
|
||||||
|
break;
|
||||||
|
case "support":
|
||||||
|
badge = "📞";
|
||||||
|
tooltip = translate("resources.users.badge.support");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badge = "👤";
|
||||||
|
tooltip = translate("resources.users.badge.regular");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (record?.admin) {
|
||||||
|
badge = "👑";
|
||||||
|
tooltip = translate("resources.users.badge.admin");
|
||||||
|
}
|
||||||
|
if (isASManaged(record?.name)) {
|
||||||
|
badge = "🛡️";
|
||||||
|
tooltip = `${translate("resources.users.badge.system_managed")} (${tooltip})`;
|
||||||
|
}
|
||||||
|
if (storage.getItem("user_id") === record?.id) {
|
||||||
|
badge = "🧙";
|
||||||
|
tooltip = `${translate("resources.users.badge.you")} (${tooltip})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is a badge, wrap the Avatar in a Badge and a Tooltip
|
||||||
|
if (badge) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip}>
|
||||||
|
<Badge
|
||||||
|
badgeContent={badge}
|
||||||
|
overlap="circular"
|
||||||
|
sx={{ "& .MuiBadge-badge": { width: "10px" } }} // we deliberately set a very small width here, to make the badge actually circular
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar alt={alt} classes={classes} sizes={sizes} src={src} sx={sx} variant={variant}>
|
||||||
|
{letter}
|
||||||
|
</Avatar>
|
||||||
|
</Badge>
|
||||||
|
</Tooltip>);
|
||||||
|
}
|
||||||
|
|
||||||
return (<Avatar alt={alt} classes={classes} sizes={sizes} src={src} sx={sx} variant={variant}>
|
return (<Avatar alt={alt} classes={classes} sizes={sizes} src={src} sx={sx} variant={variant}>
|
||||||
{letter}
|
{letter}
|
||||||
</Avatar>);
|
</Avatar>);
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { Identifier } from "ra-core";
|
import { Identifier } from "ra-core";
|
||||||
import { GetConfig } from "./config";
|
import { GetConfig } from "./config";
|
||||||
|
|
||||||
|
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if id is a valid Matrix ID (user)
|
||||||
|
* @param id The ID to check
|
||||||
|
* @returns Whether the ID is a valid Matrix ID
|
||||||
|
*/
|
||||||
|
export const isMXID = (id: string | Identifier): boolean => mxidPattern.test(id as string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -187,6 +187,14 @@ const de: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "Das Ändern eines vom System verwalteten Benutzers ist nicht zulässig.",
|
modify_managed_user_error: "Das Ändern eines vom System verwalteten Benutzers ist nicht zulässig.",
|
||||||
username_available: "Benutzername verfügbar",
|
username_available: "Benutzername verfügbar",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "Sie",
|
||||||
|
bot: "Bot",
|
||||||
|
admin: "Administrator",
|
||||||
|
support: "Unterstützung",
|
||||||
|
regular: "Normaler Benutzer",
|
||||||
|
system_managed: "Systemverwalteter Benutzer",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Lösche Benutzerdaten",
|
erase: "Lösche Benutzerdaten",
|
||||||
erase_avatar: "Avatar löschen",
|
erase_avatar: "Avatar löschen",
|
||||||
|
@ -171,6 +171,14 @@ const en: SynapseTranslationMessages = {
|
|||||||
overwrite_cancel: "Cancel",
|
overwrite_cancel: "Cancel",
|
||||||
overwrite_confirm: "Overwrite",
|
overwrite_confirm: "Overwrite",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "You",
|
||||||
|
bot: "Bot",
|
||||||
|
admin: "Admin",
|
||||||
|
support: "Support",
|
||||||
|
regular: "Regular User",
|
||||||
|
system_managed: "System-managed",
|
||||||
|
},
|
||||||
limits: {
|
limits: {
|
||||||
messages_per_second: "Messages per second",
|
messages_per_second: "Messages per second",
|
||||||
messages_per_second_text: "The number of actions that can be performed in a second.",
|
messages_per_second_text: "The number of actions that can be performed in a second.",
|
||||||
|
@ -152,6 +152,14 @@ const fa: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "لا يُسمح بتغيير المستخدم الذي يديره النظام.",
|
modify_managed_user_error: "لا يُسمح بتغيير المستخدم الذي يديره النظام.",
|
||||||
username_available: "نام کاربری موجود",
|
username_available: "نام کاربری موجود",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "شما",
|
||||||
|
bot: "ربات",
|
||||||
|
admin: "مدیر",
|
||||||
|
support: "پشتیبان",
|
||||||
|
regular: "کاربر عادی",
|
||||||
|
system_managed: "مدیریت سیستم",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "پاک کردن اطلاعات کاربر",
|
erase: "پاک کردن اطلاعات کاربر",
|
||||||
erase_avatar: "محو الصورة الرمزية",
|
erase_avatar: "محو الصورة الرمزية",
|
||||||
|
@ -155,6 +155,14 @@ const fr: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "La modification d'un utilisateur géré par le système n'est pas autorisée.",
|
modify_managed_user_error: "La modification d'un utilisateur géré par le système n'est pas autorisée.",
|
||||||
username_available: "Nom d'utilisateur disponible",
|
username_available: "Nom d'utilisateur disponible",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "Vous",
|
||||||
|
bot: "Bot",
|
||||||
|
admin: "Admin",
|
||||||
|
support: "Support",
|
||||||
|
regular: "Utilisateur régulier",
|
||||||
|
system_managed: "Géré par le système",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Effacer les données de l'utilisateur",
|
erase: "Effacer les données de l'utilisateur",
|
||||||
erase_avatar: "Effacer l'avatar",
|
erase_avatar: "Effacer l'avatar",
|
||||||
|
8
src/i18n/index.d.ts
vendored
8
src/i18n/index.d.ts
vendored
@ -163,6 +163,14 @@ interface SynapseTranslationMessages extends TranslationMessages {
|
|||||||
overwrite_cancel: string;
|
overwrite_cancel: string;
|
||||||
overwrite_confirm: string;
|
overwrite_confirm: string;
|
||||||
};
|
};
|
||||||
|
badge: {
|
||||||
|
you: string;
|
||||||
|
bot: string;
|
||||||
|
admin: string;
|
||||||
|
support: string;
|
||||||
|
regular: string;
|
||||||
|
system_managed: string;
|
||||||
|
}
|
||||||
limits: {
|
limits: {
|
||||||
messages_per_second: string;
|
messages_per_second: string;
|
||||||
messages_per_second_text: string;
|
messages_per_second_text: string;
|
||||||
|
@ -153,6 +153,14 @@ const it: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "La modifica di un utente gestito dal sistema non è consentita.",
|
modify_managed_user_error: "La modifica di un utente gestito dal sistema non è consentita.",
|
||||||
username_available: "Nome utente disponibile",
|
username_available: "Nome utente disponibile",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "Tu",
|
||||||
|
bot: "Bot",
|
||||||
|
admin: "Amministratore",
|
||||||
|
support: "Supporto",
|
||||||
|
regular: "Utente normale",
|
||||||
|
system_managed: "Gestito dal sistema",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Cancella i dati dell'utente",
|
erase: "Cancella i dati dell'utente",
|
||||||
erase_avatar: "Cancella l'avatar dell'utente",
|
erase_avatar: "Cancella l'avatar dell'utente",
|
||||||
|
516
src/i18n/ru.ts
516
src/i18n/ru.ts
@ -147,10 +147,10 @@ const ru: SynapseTranslationMessages = {
|
|||||||
helper: {
|
helper: {
|
||||||
send: "Это API удаляет локальные файлы с вашего собственного сервера, включая локальные миниатюры и копии скачанных файлов. \
|
send: "Это API удаляет локальные файлы с вашего собственного сервера, включая локальные миниатюры и копии скачанных файлов. \
|
||||||
Данный API не затрагивает файлы, загруженные во внешние хранилища.",
|
Данный API не затрагивает файлы, загруженные во внешние хранилища.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
users: {
|
users: {
|
||||||
name: "Пользователь |||| Пользователи",
|
name: "Пользователь |||| Пользователи",
|
||||||
email: "Почта",
|
email: "Почта",
|
||||||
msisdn: "Телефон",
|
msisdn: "Телефон",
|
||||||
@ -190,6 +190,14 @@ const ru: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "Изменение пользователя, управляемого системой, не допускается.",
|
modify_managed_user_error: "Изменение пользователя, управляемого системой, не допускается.",
|
||||||
username_available: "Имя пользователя доступно",
|
username_available: "Имя пользователя доступно",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "Вы",
|
||||||
|
bot: "Бот",
|
||||||
|
admin: "Админ",
|
||||||
|
support: "Поддержка",
|
||||||
|
regular: "Обычный пользователь",
|
||||||
|
system_managed: "Управляемый системой",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "Удалить данные пользователя",
|
erase: "Удалить данные пользователя",
|
||||||
erase_avatar: "Удалить аватар",
|
erase_avatar: "Удалить аватар",
|
||||||
@ -208,266 +216,266 @@ const ru: SynapseTranslationMessages = {
|
|||||||
burst_count_text: "Количество действий, которые могут быть выполнены до ограничения.",
|
burst_count_text: "Количество действий, которые могут быть выполнены до ограничения.",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rooms: {
|
rooms: {
|
||||||
name: "Комната |||| Комнаты",
|
name: "Комната |||| Комнаты",
|
||||||
fields: {
|
fields: {
|
||||||
room_id: "ID комнаты",
|
room_id: "ID комнаты",
|
||||||
name: "Название",
|
name: "Название",
|
||||||
canonical_alias: "Псевдоним",
|
canonical_alias: "Псевдоним",
|
||||||
joined_members: "Участники",
|
joined_members: "Участники",
|
||||||
joined_local_members: "Локальные участники",
|
joined_local_members: "Локальные участники",
|
||||||
joined_local_devices: "Локальные устройства",
|
joined_local_devices: "Локальные устройства",
|
||||||
state_events: "События состояния / Сложность",
|
state_events: "События состояния / Сложность",
|
||||||
version: "Версия",
|
version: "Версия",
|
||||||
is_encrypted: "Зашифровано",
|
is_encrypted: "Зашифровано",
|
||||||
encryption: "Шифрование",
|
encryption: "Шифрование",
|
||||||
federatable: "Федерация",
|
federatable: "Федерация",
|
||||||
public: "Отображается в каталоге комнат",
|
public: "Отображается в каталоге комнат",
|
||||||
creator: "Создатель",
|
creator: "Создатель",
|
||||||
join_rules: "Правила входа",
|
join_rules: "Правила входа",
|
||||||
guest_access: "Гостевой доступ",
|
guest_access: "Гостевой доступ",
|
||||||
history_visibility: "Видимость истории",
|
history_visibility: "Видимость истории",
|
||||||
topic: "Тема",
|
topic: "Тема",
|
||||||
avatar: "Аватар",
|
avatar: "Аватар",
|
||||||
actions: "Действия",
|
actions: "Действия",
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
forward_extremities:
|
|
||||||
"Оконечности — это события-листья в конце ориентированного ациклического графа (DAG) в комнате, т.е. события без дочерних элементов. \
|
|
||||||
Чем больше их в комнате, тем больше Synapse работает над разрешением состояния (это дорогостоящая операция). \
|
|
||||||
Хотя Synapse старается не допускать существования слишком большого числа таких событий в комнате, из-за ошибок они иногда снова появляются. \
|
|
||||||
Если в комнате >10 оконечностей, стоит найти комнату-виновника и попробовать удалить их с помощью SQL-запросов из #1760.",
|
|
||||||
},
|
|
||||||
enums: {
|
|
||||||
join_rules: {
|
|
||||||
public: "Для всех",
|
|
||||||
knock: "Надо постучать",
|
|
||||||
invite: "По приглашению",
|
|
||||||
private: "Приватная",
|
|
||||||
},
|
|
||||||
guest_access: {
|
|
||||||
can_join: "Гости могут войти",
|
|
||||||
forbidden: "Гости не могут войти",
|
|
||||||
},
|
|
||||||
history_visibility: {
|
|
||||||
invited: "С момента приглашения",
|
|
||||||
joined: "С момента входа",
|
|
||||||
shared: "С момента открытия доступа",
|
|
||||||
world_readable: "Для всех",
|
|
||||||
},
|
|
||||||
unencrypted: "Без шифрования",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Удалить комнату",
|
|
||||||
content:
|
|
||||||
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
|
|
||||||
fields: {
|
|
||||||
block: "Заблокировать и запретить пользователям присоединяться к комнате",
|
|
||||||
},
|
|
||||||
success: "Комната/ы успешно удалены",
|
|
||||||
failure: "Комната/ы не могут быть удалены.",
|
|
||||||
},
|
|
||||||
make_admin: {
|
|
||||||
assign_admin: "Назначить администратора",
|
|
||||||
title: "Назначить администратора комнате %{roomName}",
|
|
||||||
confirm: "Назначить администратора",
|
|
||||||
content: "Введите полную MXID пользователя, которого нужно назначить администратором.\nПредупреждение: для этого должен быть назначен хотя бы один локальный участник в качестве администратора.",
|
|
||||||
success: "Пользователь назначен администратором комнаты.",
|
|
||||||
failure: "Пользователь не может быть назначен администратором комнаты. %{errMsg}",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
reports: {
|
helper: {
|
||||||
name: "Жалоба |||| Жалобы",
|
forward_extremities:
|
||||||
fields: {
|
"Оконечности — это события-листья в конце ориентированного ациклического графа (DAG) в комнате, т.е. события без дочерних элементов. \
|
||||||
id: "ID",
|
Чем больше их в комнате, тем больше Synapse работает над разрешением состояния (это дорогостоящая операция). \
|
||||||
received_ts: "Дата и время жалобы",
|
Хотя Synapse старается не допускать существования слишком большого числа таких событий в комнате, из-за ошибок они иногда снова появляются. \
|
||||||
user_id: "Автор жалобы",
|
Если в комнате >10 оконечностей, стоит найти комнату-виновника и попробовать удалить их с помощью SQL-запросов из #1760.",
|
||||||
name: "Название комнаты",
|
|
||||||
score: "Баллы",
|
|
||||||
reason: "Причина",
|
|
||||||
event_id: "ID события",
|
|
||||||
event_json: {
|
|
||||||
origin: "Исходнный сервер",
|
|
||||||
origin_server_ts: "Дата и время отправки",
|
|
||||||
type: "Тип события",
|
|
||||||
content: {
|
|
||||||
msgtype: "Тип содержимого",
|
|
||||||
body: "Содержимое",
|
|
||||||
format: "Формат",
|
|
||||||
formatted_body: "Форматированное содержимое",
|
|
||||||
algorithm: "Алгоритм",
|
|
||||||
url: "Ссылка",
|
|
||||||
info: {
|
|
||||||
mimetype: "Тип",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Удалить жалобу",
|
|
||||||
content: "Действительно удалить жалобу? Это действие будет невозможно отменить.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
connections: {
|
enums: {
|
||||||
name: "Подключения",
|
join_rules: {
|
||||||
fields: {
|
public: "Для всех",
|
||||||
last_seen: "Дата",
|
knock: "Надо постучать",
|
||||||
ip: "IP адрес",
|
invite: "По приглашению",
|
||||||
user_agent: "Юзер-агент",
|
private: "Приватная",
|
||||||
},
|
},
|
||||||
|
guest_access: {
|
||||||
|
can_join: "Гости могут войти",
|
||||||
|
forbidden: "Гости не могут войти",
|
||||||
|
},
|
||||||
|
history_visibility: {
|
||||||
|
invited: "С момента приглашения",
|
||||||
|
joined: "С момента входа",
|
||||||
|
shared: "С момента открытия доступа",
|
||||||
|
world_readable: "Для всех",
|
||||||
|
},
|
||||||
|
unencrypted: "Без шифрования",
|
||||||
},
|
},
|
||||||
devices: {
|
action: {
|
||||||
name: "Устройство |||| Устройства",
|
erase: {
|
||||||
fields: {
|
title: "Удалить комнату",
|
||||||
device_id: "ID устройства",
|
|
||||||
display_name: "Название",
|
|
||||||
last_seen_ts: "Дата и время",
|
|
||||||
last_seen_ip: "IP адрес",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
erase: {
|
|
||||||
title: "Удаление %{id}",
|
|
||||||
content: 'Действительно удалить устройство "%{name}"?',
|
|
||||||
success: "Устройство успешно удалено.",
|
|
||||||
failure: "Произошла ошибка.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users_media: {
|
|
||||||
name: "Файлы",
|
|
||||||
fields: {
|
|
||||||
media_id: "ID файла",
|
|
||||||
media_length: "Размер файла (в байтах)",
|
|
||||||
media_type: "Тип",
|
|
||||||
upload_name: "Имя файла",
|
|
||||||
quarantined_by: "На карантине",
|
|
||||||
safe_from_quarantine: "Защитить от карантина",
|
|
||||||
created_ts: "Создано",
|
|
||||||
last_access_ts: "Последний доступ",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
open: "Открыть файл в новом окне",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
protect_media: {
|
|
||||||
action: {
|
|
||||||
create: "Не защищён, установить защиту",
|
|
||||||
delete: "Защищён, снять защиту",
|
|
||||||
none: "На карантине",
|
|
||||||
send_success: "Статус защиты успешно изменён.",
|
|
||||||
send_failure: "Произошла ошибка.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quarantine_media: {
|
|
||||||
action: {
|
|
||||||
name: "Карантин",
|
|
||||||
create: "Поместить на карантин",
|
|
||||||
delete: "На карантине, снять карантин",
|
|
||||||
none: "Защищено от карантина",
|
|
||||||
send_success: "Статус карантина успешно изменён.",
|
|
||||||
send_failure: "Произошла ошибка.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pushers: {
|
|
||||||
name: "Пушер |||| Пушеры",
|
|
||||||
fields: {
|
|
||||||
app: "Приложение",
|
|
||||||
app_display_name: "Название приложения",
|
|
||||||
app_id: "ID приложения",
|
|
||||||
device_display_name: "Название устройства",
|
|
||||||
kind: "Вид",
|
|
||||||
lang: "Язык",
|
|
||||||
profile_tag: "Тег профиля",
|
|
||||||
pushkey: "Ключ",
|
|
||||||
data: { url: "URL" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
servernotices: {
|
|
||||||
name: "Серверные уведомления",
|
|
||||||
send: "Отправить серверные уведомления",
|
|
||||||
fields: {
|
|
||||||
body: "Сообщение",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
send: "Отправить",
|
|
||||||
send_success: "Серверное уведомление успешно отправлено.",
|
|
||||||
send_failure: "Произошла ошибка.",
|
|
||||||
},
|
|
||||||
helper: {
|
|
||||||
send: 'Отправить серверное уведомление выбранным пользователям. На сервере должна быть активна функция "Server Notices".',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
user_media_statistics: {
|
|
||||||
name: "Файлы пользователей",
|
|
||||||
fields: {
|
|
||||||
media_count: "Количество файлов",
|
|
||||||
media_length: "Размер файлов",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
forward_extremities: {
|
|
||||||
name: "Оконечности",
|
|
||||||
fields: {
|
|
||||||
id: "ID события",
|
|
||||||
received_ts: "Дата и время",
|
|
||||||
depth: "Глубина",
|
|
||||||
state_group: "Группа состояния",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_state: {
|
|
||||||
name: "События состояния",
|
|
||||||
fields: {
|
|
||||||
type: "Тип",
|
|
||||||
content: "Содержимое",
|
|
||||||
origin_server_ts: "Дата отправки",
|
|
||||||
sender: "Отправитель",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
room_directory: {
|
|
||||||
name: "Каталог комнат",
|
|
||||||
fields: {
|
|
||||||
world_readable: "Гости могут просматривать без входа",
|
|
||||||
guest_can_join: "Гости могут войти",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
title:
|
|
||||||
"Удалить комнату из каталога |||| Удалить %{smart_count} комнаты из каталога |||| Удалить %{smart_count} комнат из каталога",
|
|
||||||
content:
|
content:
|
||||||
"Действительно удалить комнату из каталога? |||| Действительно удалить %{smart_count} комнаты из каталога? |||| Действительно удалить %{smart_count} комнат из каталога?",
|
"Действительно удалить эту комнату? Это действие будет невозможно отменить. Все сообщения и файлы в комнате будут удалены с сервера!",
|
||||||
erase: "Удалить из каталога комнат",
|
fields: {
|
||||||
create: "Опубликовать в каталоге комнат",
|
block: "Заблокировать и запретить пользователям присоединяться к комнате",
|
||||||
send_success: "Комната успешно опубликована.",
|
},
|
||||||
send_failure: "Произошла ошибка.",
|
success: "Комната/ы успешно удалены",
|
||||||
|
failure: "Комната/ы не могут быть удалены.",
|
||||||
|
},
|
||||||
|
make_admin: {
|
||||||
|
assign_admin: "Назначить администратора",
|
||||||
|
title: "Назначить администратора комнате %{roomName}",
|
||||||
|
confirm: "Назначить администратора",
|
||||||
|
content: "Введите полную MXID пользователя, которого нужно назначить администратором.\nПредупреждение: для этого должен быть назначен хотя бы один локальный участник в качестве администратора.",
|
||||||
|
success: "Пользователь назначен администратором комнаты.",
|
||||||
|
failure: "Пользователь не может быть назначен администратором комнаты. %{errMsg}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
name: "Жалоба |||| Жалобы",
|
||||||
|
fields: {
|
||||||
|
id: "ID",
|
||||||
|
received_ts: "Дата и время жалобы",
|
||||||
|
user_id: "Автор жалобы",
|
||||||
|
name: "Название комнаты",
|
||||||
|
score: "Баллы",
|
||||||
|
reason: "Причина",
|
||||||
|
event_id: "ID события",
|
||||||
|
event_json: {
|
||||||
|
origin: "Исходнный сервер",
|
||||||
|
origin_server_ts: "Дата и время отправки",
|
||||||
|
type: "Тип события",
|
||||||
|
content: {
|
||||||
|
msgtype: "Тип содержимого",
|
||||||
|
body: "Содержимое",
|
||||||
|
format: "Формат",
|
||||||
|
formatted_body: "Форматированное содержимое",
|
||||||
|
algorithm: "Алгоритм",
|
||||||
|
url: "Ссылка",
|
||||||
|
info: {
|
||||||
|
mimetype: "Тип",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
destinations: {
|
action: {
|
||||||
name: "Федерация",
|
erase: {
|
||||||
fields: {
|
title: "Удалить жалобу",
|
||||||
destination: "Назначение",
|
content: "Действительно удалить жалобу? Это действие будет невозможно отменить.",
|
||||||
failure_ts: "Дата и время ошибки",
|
|
||||||
retry_last_ts: "Дата и время последней попытки",
|
|
||||||
retry_interval: "Интервал между попытками",
|
|
||||||
last_successful_stream_ordering: "Последний успешный поток",
|
|
||||||
stream_ordering: "Поток",
|
|
||||||
},
|
},
|
||||||
action: { reconnect: "Переподключиться" },
|
|
||||||
},
|
},
|
||||||
registration_tokens: {
|
},
|
||||||
name: "Токены регистрации",
|
connections: {
|
||||||
fields: {
|
name: "Подключения",
|
||||||
token: "Токен",
|
fields: {
|
||||||
valid: "Рабочий токен",
|
last_seen: "Дата",
|
||||||
uses_allowed: "Количество использований",
|
ip: "IP адрес",
|
||||||
pending: "Ожидает",
|
user_agent: "Юзер-агент",
|
||||||
completed: "Завершено",
|
},
|
||||||
expiry_time: "Дата окончания",
|
},
|
||||||
length: "Длина",
|
devices: {
|
||||||
|
name: "Устройство |||| Устройства",
|
||||||
|
fields: {
|
||||||
|
device_id: "ID устройства",
|
||||||
|
display_name: "Название",
|
||||||
|
last_seen_ts: "Дата и время",
|
||||||
|
last_seen_ip: "IP адрес",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
erase: {
|
||||||
|
title: "Удаление %{id}",
|
||||||
|
content: 'Действительно удалить устройство "%{name}"?',
|
||||||
|
success: "Устройство успешно удалено.",
|
||||||
|
failure: "Произошла ошибка.",
|
||||||
},
|
},
|
||||||
helper: { length: "Длина токена, если токен не задан." },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
users_media: {
|
||||||
|
name: "Файлы",
|
||||||
|
fields: {
|
||||||
|
media_id: "ID файла",
|
||||||
|
media_length: "Размер файла (в байтах)",
|
||||||
|
media_type: "Тип",
|
||||||
|
upload_name: "Имя файла",
|
||||||
|
quarantined_by: "На карантине",
|
||||||
|
safe_from_quarantine: "Защитить от карантина",
|
||||||
|
created_ts: "Создано",
|
||||||
|
last_access_ts: "Последний доступ",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
open: "Открыть файл в новом окне",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
protect_media: {
|
||||||
|
action: {
|
||||||
|
create: "Не защищён, установить защиту",
|
||||||
|
delete: "Защищён, снять защиту",
|
||||||
|
none: "На карантине",
|
||||||
|
send_success: "Статус защиты успешно изменён.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quarantine_media: {
|
||||||
|
action: {
|
||||||
|
name: "Карантин",
|
||||||
|
create: "Поместить на карантин",
|
||||||
|
delete: "На карантине, снять карантин",
|
||||||
|
none: "Защищено от карантина",
|
||||||
|
send_success: "Статус карантина успешно изменён.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pushers: {
|
||||||
|
name: "Пушер |||| Пушеры",
|
||||||
|
fields: {
|
||||||
|
app: "Приложение",
|
||||||
|
app_display_name: "Название приложения",
|
||||||
|
app_id: "ID приложения",
|
||||||
|
device_display_name: "Название устройства",
|
||||||
|
kind: "Вид",
|
||||||
|
lang: "Язык",
|
||||||
|
profile_tag: "Тег профиля",
|
||||||
|
pushkey: "Ключ",
|
||||||
|
data: { url: "URL" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
servernotices: {
|
||||||
|
name: "Серверные уведомления",
|
||||||
|
send: "Отправить серверные уведомления",
|
||||||
|
fields: {
|
||||||
|
body: "Сообщение",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
send: "Отправить",
|
||||||
|
send_success: "Серверное уведомление успешно отправлено.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
send: 'Отправить серверное уведомление выбранным пользователям. На сервере должна быть активна функция "Server Notices".',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_media_statistics: {
|
||||||
|
name: "Файлы пользователей",
|
||||||
|
fields: {
|
||||||
|
media_count: "Количество файлов",
|
||||||
|
media_length: "Размер файлов",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
forward_extremities: {
|
||||||
|
name: "Оконечности",
|
||||||
|
fields: {
|
||||||
|
id: "ID события",
|
||||||
|
received_ts: "Дата и время",
|
||||||
|
depth: "Глубина",
|
||||||
|
state_group: "Группа состояния",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_state: {
|
||||||
|
name: "События состояния",
|
||||||
|
fields: {
|
||||||
|
type: "Тип",
|
||||||
|
content: "Содержимое",
|
||||||
|
origin_server_ts: "Дата отправки",
|
||||||
|
sender: "Отправитель",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
room_directory: {
|
||||||
|
name: "Каталог комнат",
|
||||||
|
fields: {
|
||||||
|
world_readable: "Гости могут просматривать без входа",
|
||||||
|
guest_can_join: "Гости могут войти",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
title:
|
||||||
|
"Удалить комнату из каталога |||| Удалить %{smart_count} комнаты из каталога |||| Удалить %{smart_count} комнат из каталога",
|
||||||
|
content:
|
||||||
|
"Действительно удалить комнату из каталога? |||| Действительно удалить %{smart_count} комнаты из каталога? |||| Действительно удалить %{smart_count} комнат из каталога?",
|
||||||
|
erase: "Удалить из каталога комнат",
|
||||||
|
create: "Опубликовать в каталоге комнат",
|
||||||
|
send_success: "Комната успешно опубликована.",
|
||||||
|
send_failure: "Произошла ошибка.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destinations: {
|
||||||
|
name: "Федерация",
|
||||||
|
fields: {
|
||||||
|
destination: "Назначение",
|
||||||
|
failure_ts: "Дата и время ошибки",
|
||||||
|
retry_last_ts: "Дата и время последней попытки",
|
||||||
|
retry_interval: "Интервал между попытками",
|
||||||
|
last_successful_stream_ordering: "Последний успешный поток",
|
||||||
|
stream_ordering: "Поток",
|
||||||
|
},
|
||||||
|
action: { reconnect: "Переподключиться" },
|
||||||
|
},
|
||||||
|
registration_tokens: {
|
||||||
|
name: "Токены регистрации",
|
||||||
|
fields: {
|
||||||
|
token: "Токен",
|
||||||
|
valid: "Рабочий токен",
|
||||||
|
uses_allowed: "Количество использований",
|
||||||
|
pending: "Ожидает",
|
||||||
|
completed: "Завершено",
|
||||||
|
expiry_time: "Дата окончания",
|
||||||
|
length: "Длина",
|
||||||
|
},
|
||||||
|
helper: { length: "Длина токена, если токен не задан." },
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export default ru;
|
export default ru;
|
||||||
|
@ -178,6 +178,14 @@ const zh: SynapseTranslationMessages = {
|
|||||||
modify_managed_user_error: "不允许修改系统管理的用户。",
|
modify_managed_user_error: "不允许修改系统管理的用户。",
|
||||||
username_available: "用户名可用",
|
username_available: "用户名可用",
|
||||||
},
|
},
|
||||||
|
badge: {
|
||||||
|
you: "您",
|
||||||
|
bot: "机器人",
|
||||||
|
admin: "管理员",
|
||||||
|
support: "支持",
|
||||||
|
regular: "普通用户",
|
||||||
|
system_managed: "系统管理",
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
erase: "抹除用户信息",
|
erase: "抹除用户信息",
|
||||||
erase_avatar: "抹掉头像",
|
erase_avatar: "抹掉头像",
|
||||||
|
@ -320,9 +320,6 @@ const UserTitle = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let username = record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""
|
let username = record ? (record.displayname ? `"${record.displayname}"` : `"${record.name}"`) : ""
|
||||||
if (isASManaged(record?.id)) {
|
|
||||||
username += " 🤖";
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{translate("resources.users.name", {
|
{translate("resources.users.name", {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Identifier, fetchUtils } from "react-admin";
|
import { Identifier, fetchUtils } from "react-admin";
|
||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
|
import { isMXID } from "../components/mxid";
|
||||||
|
|
||||||
export const splitMxid = mxid => {
|
export const splitMxid = mxid => {
|
||||||
const re = /^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
|
const re = /^@(?<name>[a-zA-Z0-9._=\-/]+):(?<domain>[a-zA-Z0-9\-.]+\.[a-zA-Z]+)$/;
|
||||||
@ -77,8 +78,8 @@ export function returnMXID(input: string | Identifier): string {
|
|||||||
|
|
||||||
// Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":")
|
// Check if the input already looks like a valid MXID (i.e., starts with "@" and contains ":")
|
||||||
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
const mxidPattern = /^@[^@:]+:[^@:]+$/;
|
||||||
if (typeof input === 'string' && mxidPattern.test(input)) {
|
if (isMXID(input)) {
|
||||||
return input; // Already a valid MXID
|
return input as string; // Already a valid MXID
|
||||||
}
|
}
|
||||||
|
|
||||||
// If input is not a valid MXID, assume it's a localpart and construct the MXID
|
// If input is not a valid MXID, assume it's a localpart and construct the MXID
|
||||||
|
Loading…
x
Reference in New Issue
Block a user