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",
|
||||||
|
@ -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: "Удалить аватар",
|
||||||
@ -468,6 +476,6 @@ const ru: SynapseTranslationMessages = {
|
|||||||
},
|
},
|
||||||
helper: { 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