0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

feat(console): suspend user (#3696)

This commit is contained in:
Xiao Yijun 2023-04-14 16:44:37 +08:00 committed by GitHub
parent 9cedac95cb
commit 5553425fcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 339 additions and 56 deletions

View file

@ -0,0 +1,6 @@
---
"@logto/console": minor
"@logto/phrases": minor
---
support suspend user

View file

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 1.66663C8.35191 1.66663 6.74074 2.15537 5.37033 3.07105C3.99992 3.98672 2.93182 5.28821 2.30109 6.81093C1.67036 8.33365 1.50533 10.0092 1.82687 11.6257C2.14842 13.2422 2.94209 14.7271 4.10753 15.8925C5.27297 17.058 6.75782 17.8516 8.37433 18.1732C9.99084 18.4947 11.6664 18.3297 13.1891 17.699C14.7118 17.0682 16.0133 16.0001 16.929 14.6297C17.8447 13.2593 18.3334 11.6481 18.3334 9.99996C18.3334 8.90561 18.1179 7.82198 17.6991 6.81093C17.2803 5.79988 16.6665 4.88122 15.8926 4.1074C15.1188 3.33358 14.2002 2.71975 13.1891 2.30096C12.1781 1.88217 11.0944 1.66663 10.0001 1.66663ZM10.0001 16.6666C8.23198 16.6666 6.53628 15.9642 5.28604 14.714C4.0358 13.4638 3.33342 11.7681 3.33342 9.99996C3.33158 8.51941 3.8276 7.08125 4.74175 5.91663L14.0834 15.2583C12.9188 16.1724 11.4806 16.6685 10.0001 16.6666ZM15.2584 14.0833L5.91675 4.74163C7.08137 3.82747 8.51954 3.33145 10.0001 3.33329C11.7682 3.33329 13.4639 4.03567 14.7141 5.28591C15.9644 6.53616 16.6668 8.23185 16.6668 9.99996C16.6686 11.4805 16.1726 12.9187 15.2584 14.0833Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.3585 3.04172C16.2617 2.9634 16.1486 2.90778 16.0274 2.87894C15.9063 2.8501 15.7802 2.84877 15.6585 2.87505C14.768 3.06167 13.8488 3.06405 12.9574 2.88204C12.0659 2.70003 11.2212 2.33748 10.4752 1.81672C10.3357 1.71995 10.1699 1.66809 10.0002 1.66809C9.83039 1.66809 9.66466 1.71995 9.52517 1.81672C8.77913 2.33748 7.9344 2.70003 7.04297 2.88204C6.15155 3.06405 5.2323 3.06167 4.34183 2.87505C4.22012 2.84877 4.09406 2.8501 3.97292 2.87894C3.85178 2.90778 3.73865 2.9634 3.64183 3.04172C3.54515 3.12015 3.46727 3.21925 3.41391 3.33173C3.36055 3.44422 3.33308 3.56722 3.3335 3.69172V9.90005C3.33276 11.0948 3.61748 12.2725 4.16395 13.335C4.71041 14.3975 5.50282 15.3141 6.47517 16.0084L9.51683 18.1751C9.65797 18.2755 9.82691 18.3295 10.0002 18.3295C10.1734 18.3295 10.3424 18.2755 10.4835 18.1751L13.5252 16.0084C14.4975 15.3141 15.2899 14.3975 15.8364 13.335C16.3829 12.2725 16.6676 11.0948 16.6668 9.90005V3.69172C16.6673 3.56722 16.6398 3.44422 16.5864 3.33173C16.5331 3.21925 16.4552 3.12015 16.3585 3.04172ZM15.0002 9.90005C15.0008 10.829 14.7796 11.7447 14.3549 12.5709C13.9302 13.3971 13.3143 14.1099 12.5585 14.6501L10.0002 16.4751L7.44183 14.6501C6.68603 14.1099 6.07015 13.3971 5.64546 12.5709C5.22076 11.7447 4.99953 10.829 5.00017 9.90005V4.65005C6.74719 4.79958 8.49687 4.39424 10.0002 3.49172C11.5035 4.39424 13.2531 4.79958 15.0002 4.65005V9.90005ZM11.2835 7.99172L9.04183 10.2417L8.30017 9.49172C8.14325 9.3348 7.93042 9.24664 7.7085 9.24664C7.48658 9.24664 7.27375 9.3348 7.11683 9.49172C6.95991 9.64864 6.87176 9.86147 6.87176 10.0834C6.87176 10.3053 6.95991 10.5181 7.11683 10.6751L8.45017 12.0084C8.52764 12.0865 8.6198 12.1485 8.72135 12.1908C8.8229 12.2331 8.93182 12.2549 9.04183 12.2549C9.15184 12.2549 9.26077 12.2331 9.36231 12.1908C9.46386 12.1485 9.55603 12.0865 9.6335 12.0084L12.5002 9.16672C12.6571 9.0098 12.7452 8.79697 12.7452 8.57505C12.7452 8.35314 12.6571 8.14031 12.5002 7.98339C12.3432 7.82647 12.1304 7.73831 11.9085 7.73831C11.6866 7.73831 11.4738 7.82647 11.3168 7.98339L11.2835 7.99172Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -12,29 +12,45 @@
.content {
padding-right: _.unit(4);
overflow: hidden;
display: flex;
align-items: center;
// Note: add an offset to visually center the element.
margin-top: _.unit(-1);
.title {
display: block;
font: var(--font-body-2);
color: var(--color-text-link);
text-decoration: none;
@include _.text-ellipsis;
> div:not(:last-child) {
margin-right: _.unit(2);
}
.subtitle {
font: var(--font-body-3);
color: var(--color-text-secondary);
@include _.text-ellipsis;
.meta {
overflow: hidden;
.title {
display: block;
font: var(--font-body-2);
color: var(--color-text-link);
text-decoration: none;
@include _.text-ellipsis;
}
.subtitle {
font: var(--font-body-3);
color: var(--color-text-secondary);
@include _.text-ellipsis;
}
}
}
&.compact {
.content {
display: flex;
align-items: baseline;
margin-top: unset;
.title {
margin-right: _.unit(1);
.meta {
display: flex;
align-items: baseline;
.title {
margin-right: _.unit(1);
}
}
}
}

View file

@ -11,26 +11,30 @@ type Props = {
icon?: ReactNode;
to?: To;
size?: 'default' | 'compact';
suffix?: ReactNode;
};
function ItemPreview({ title, subtitle, icon, to, size = 'default' }: Props) {
function ItemPreview({ title, subtitle, icon, to, size = 'default', suffix }: Props) {
return (
<div className={classNames(styles.item, styles[size])}>
{icon}
<div className={styles.content}>
{to && (
<Link
className={styles.title}
to={to}
onClick={(event) => {
event.stopPropagation();
}}
>
{title}
</Link>
)}
{!to && <div className={styles.title}>{title}</div>}
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
<div className={styles.meta}>
{to && (
<Link
className={styles.title}
to={to}
onClick={(event) => {
event.stopPropagation();
}}
>
{title}
</Link>
)}
{!to && <div className={styles.title}>{title}</div>}
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
</div>
{suffix}
</div>
</div>
);

View file

@ -12,6 +12,11 @@
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
max-width: fit-content;
}
.suspended {
margin-left: _.unit(1);
}
&:hover {

View file

@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import Checkbox from '@/components/Checkbox';
import UserAvatar from '@/components/UserAvatar';
import SuspendedTag from '@/pages/Users/components/SuspendedTag';
import { onKeyDownHandler } from '@/utils/a11y';
import * as styles from './index.module.scss';
@ -36,6 +37,7 @@ function SourceUserItem({ user, isSelected, onSelect }: Props) {
/>
<UserAvatar user={user} size="micro" />
<div className={styles.name}>{user.name ?? t('users.unnamed')}</div>
{user.isSuspended && <SuspendedTag className={styles.suspended} />}
</div>
);
}

View file

@ -6,11 +6,23 @@
padding: _.unit(2.5) _.unit(4);
user-select: none;
.name {
flex: 1 1 0;
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
.meta {
flex: 1;
display: flex;
align-items: center;
overflow: hidden;
.name {
flex: 1 1 0;
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
max-width: fit-content;
}
.suspended {
margin: 0 _.unit(1);
}
}
.icon {

View file

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import Close from '@/assets/images/close.svg';
import IconButton from '@/components/IconButton';
import UserAvatar from '@/components/UserAvatar';
import SuspendedTag from '@/pages/Users/components/SuspendedTag';
import * as styles from './index.module.scss';
@ -17,8 +18,11 @@ function TargetUserItem({ user, onDelete }: Props) {
return (
<div className={styles.item}>
<UserAvatar user={user} size="micro" />
<div className={styles.name}>{user.name ?? t('users.unnamed')}</div>
<div className={styles.meta}>
<UserAvatar user={user} size="micro" />
<div className={styles.name}>{user.name ?? t('users.unnamed')}</div>
{user.isSuspended && <SuspendedTag className={styles.suspended} />}
</div>
<IconButton
size="small"
iconClassName={styles.icon}

View file

@ -23,6 +23,7 @@ import { defaultPageSize } from '@/consts';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import SuspendedTag from '@/pages/Users/components/SuspendedTag';
import { buildUrl, formatSearchKeyword } from '@/utils/url';
import type { RoleDetailsOutletContext } from '../types';
@ -92,7 +93,7 @@ function RoleUsers() {
dataIndex: 'name',
colSpan: 5,
render: (user) => {
const { id, name } = user;
const { id, name, isSuspended } = user;
return (
<ItemPreview
@ -101,6 +102,7 @@ function RoleUsers() {
icon={<UserAvatar user={user} />}
to={`/users/${id}`}
size="compact"
suffix={conditional(isSuspended && <SuspendedTag />)}
/>
);
},

View file

@ -54,13 +54,8 @@
height: 12px;
}
}
.moreIcon {
color: var(--color-text-secondary);
}
}
.resetIcon {
.icon {
color: var(--color-text-secondary);
}

View file

@ -9,10 +9,13 @@ import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
import useSWR from 'swr';
import Delete from '@/assets/images/delete.svg';
import Forbidden from '@/assets/images/forbidden.svg';
import More from '@/assets/images/more.svg';
import Reset from '@/assets/images/reset.svg';
import Shield from '@/assets/images/shield.svg';
import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu';
import Card from '@/components/Card';
import ConfirmModal from '@/components/ConfirmModal';
import CopyToClipboard from '@/components/CopyToClipboard';
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
import DetailsPage from '@/components/DetailsPage';
@ -25,6 +28,7 @@ import useApi from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss';
import UserAccountInformation from '../../components/UserAccountInformation';
import SuspendedTag from '../Users/components/SuspendedTag';
import ResetPasswordForm from './components/ResetPasswordForm';
import * as styles from './index.module.scss';
@ -39,9 +43,12 @@ function UserDetails() {
const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isResetPasswordFormOpen, setIsResetPasswordFormOpen] = useState(false);
const [isToggleSuspendFormOpen, setIsToggleSuspendFormOpen] = useState(false);
const [isUpdatingSuspendState, setIsUpdatingSuspendState] = useState(false);
const [resetResult, setResetResult] = useState<string>();
const { data, error, mutate } = useSWR<User, RequestError>(id && `api/users/${id}`);
const { isSuspended: isSuspendedUser = false } = data ?? {};
const isLoading = !data && !error;
const api = useApi();
const navigate = useNavigate();
@ -49,6 +56,7 @@ function UserDetails() {
useEffect(() => {
setIsDeleteFormOpen(false);
setIsResetPasswordFormOpen(false);
setIsToggleSuspendFormOpen(false);
}, [pathname]);
const onDelete = async () => {
@ -67,6 +75,27 @@ function UserDetails() {
}
};
const onToggleSuspendState = async () => {
if (!data || isUpdatingSuspendState) {
return;
}
setIsUpdatingSuspendState(true);
try {
const updatedUser = await api
.patch(`api/users/${data.id}/is-suspended`, { json: { isSuspended: !isSuspendedUser } })
.json<User>();
void mutate(updatedUser);
setIsToggleSuspendFormOpen(false);
toast.success(
t(updatedUser.isSuspended ? 'user_details.user_suspended' : 'user_details.user_reactivated')
);
} finally {
setIsUpdatingSuspendState(false);
}
};
return (
<DetailsPage
backLink="/users"
@ -84,9 +113,7 @@ function UserDetails() {
<div className={styles.metadata}>
<div className={styles.name}>{data.name ?? '-'}</div>
<div>
{data.isSuspended && (
<div className={styles.suspended}>{t('user_details.suspended')}</div>
)}
{isSuspendedUser && <SuspendedTag />}
{data.username && (
<>
<div className={styles.username}>{data.username}</div>
@ -99,18 +126,29 @@ function UserDetails() {
</div>
<div>
<ActionMenu
buttonProps={{ icon: <More className={styles.moreIcon} />, size: 'large' }}
buttonProps={{ icon: <More className={styles.icon} />, size: 'large' }}
title={t('general.more_options')}
>
<ActionMenuItem
icon={<Reset />}
iconClassName={styles.resetIcon}
iconClassName={styles.icon}
onClick={() => {
setIsResetPasswordFormOpen(true);
}}
>
{t('user_details.reset_password.reset_password')}
</ActionMenuItem>
<ActionMenuItem
icon={isSuspendedUser ? <Shield /> : <Forbidden />}
iconClassName={styles.icon}
onClick={() => {
setIsToggleSuspendFormOpen(true);
}}
>
{t(
isSuspendedUser ? 'user_details.reactivate_user' : 'user_details.suspend_user'
)}
</ActionMenuItem>
<ActionMenuItem
icon={<Delete />}
type="danger"
@ -151,6 +189,23 @@ function UserDetails() {
>
<div>{t('user_details.delete_description')}</div>
</DeleteConfirmModal>
<ConfirmModal
isOpen={isToggleSuspendFormOpen}
isLoading={isUpdatingSuspendState}
confirmButtonText={
isSuspendedUser ? 'user_details.reactivate_action' : 'user_details.suspend_action'
}
onCancel={() => {
setIsToggleSuspendFormOpen(false);
}}
onConfirm={onToggleSuspendState}
>
{t(
isSuspendedUser
? 'user_details.reactivate_user_reminder'
: 'user_details.suspend_user_reminder'
)}
</ConfirmModal>
</div>
</Card>
<TabNav>

View file

@ -0,0 +1,9 @@
@use '@/scss/underscore' as _;
.suspended {
background: var(--color-error-container);
color: var(--color-text);
font: var(--font-label-3);
padding: _.unit(0.5) _.unit(1.5);
border-radius: 10px;
}

View file

@ -0,0 +1,18 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';
type Props = {
className?: string;
};
function SuspendedTag({ className }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div className={classNames(styles.suspended, className)}>{t('user_details.suspended')}</div>
);
}
export default SuspendedTag;

View file

@ -26,6 +26,7 @@ import * as resourcesStyles from '@/scss/resources.module.scss';
import { buildUrl, formatSearchKeyword } from '@/utils/url';
import CreateForm from './components/CreateForm';
import SuspendedTag from './components/SuspendedTag';
import * as styles from './index.module.scss';
const pageSize = defaultPageSize;
@ -97,7 +98,7 @@ function Users() {
dataIndex: 'name',
colSpan: 6,
render: (user) => {
const { id, name } = user;
const { id, name, isSuspended } = user;
return (
<ItemPreview
@ -106,6 +107,7 @@ function Users() {
icon={<UserAvatar user={user} />}
to={buildDetailsPathname(id)}
size="compact"
suffix={conditional(isSuspended && <SuspendedTag />)}
/>
);
},

View file

@ -40,9 +40,19 @@ const user_details = {
remove: 'Löschen',
not_connected: 'Der Nutzer ist nicht mit einem Social Connector verbunden',
deletion_confirmation:
'Du entfernst die bestehende <name/> Identität. Bist du sicher, dass du das tun willst?',
'Du entfernst die bestehende <name/> Identität. Bist du sicher, dass du das tun möchtest?',
},
suspended: 'Gesperrt',
suspend_user: 'Benutzer sperren',
suspend_user_reminder:
'Sind Sie sicher, dass Sie diesen Benutzer sperren möchten? Der Benutzer kann sich nicht bei Ihrer App anmelden und kann nach Ablauf des aktuellen Tokens kein neues Zugriffstoken mehr erhalten. Außerdem schlagen alle API-Anforderungen von diesem Benutzer fehl.',
suspend_action: 'Sperren',
user_suspended: 'Benutzer wurde gesperrt.',
reactivate_user: 'Benutzer entsperren',
reactivate_user_reminder:
'Sind Sie sicher, dass Sie diesen Benutzer wieder aktivieren möchten? Dadurch werden alle Anmeldeversuche für diesen Benutzer ermöglicht.',
reactivate_action: 'Aktivieren',
user_reactivated: 'Benutzer wurde aktiviert.',
roles: {
name_column: 'Rolle',
description_column: 'Beschreibung',

View file

@ -41,6 +41,16 @@ const user_details = {
'You are removing the existing <name/> identity. Are you sure you want to do that?',
},
suspended: 'Suspended',
suspend_user: 'Suspend user',
suspend_user_reminder:
'Are you sure you want to suspend this user? The user will be unable to sign in to your app and wont be able to obtain a new access token after the current one expires. Additionally, any API requests made by this user will fail.',
suspend_action: 'Suspend',
user_suspended: 'User has been suspended.',
reactivate_user: 'Reactivate user',
reactivate_user_reminder:
'Are you sure you want to reactivate this user? Doing so will permit any sign-in attempts for this user.',
reactivate_action: 'Reactivate',
user_reactivated: 'User has been reactivated.',
roles: {
name_column: 'Role',
description_column: 'Description',

View file

@ -1,13 +1,13 @@
const user_details = {
page_title: 'Detalles de usuario',
back_to_users: 'Volver a la gestión de usuarios',
created_title: 'Se ha creado el usuario con éxito',
created_title: 'Usuario creado con éxito',
created_guide: 'Puede enviar al usuario la siguiente información de inicio de sesión',
created_username: 'Nombre de usuario:',
created_password: 'Contraseña:',
menu_delete: 'Eliminar',
delete_description: 'Esta acción no se puede deshacer. Eliminará permanentemente al usuario.',
deleted: 'Se ha eliminado al usuario con éxito',
deleted: 'Usuario eliminado con éxito',
reset_password: {
reset_password: 'Restablecer contraseña',
title: '¿Está seguro de que desea restablecer la contraseña?',
@ -42,6 +42,16 @@ const user_details = {
'Está eliminando la identidad de <name/> existente. ¿Está seguro de que desea hacer esto?',
},
suspended: 'Suspendido',
suspend_user: 'Suspender usuario',
suspend_user_reminder:
'¿Está seguro de que desea suspender a este usuario? El usuario no podrá iniciar sesión en su aplicación y no podrá obtener un nuevo token de acceso después de que expire el actual. Además, todas las solicitudes de API realizadas por este usuario fallarán.',
suspend_action: 'Suspender',
user_suspended: 'El usuario ha sido suspendido.',
reactivate_user: 'Reactivar usuario',
reactivate_user_reminder:
'¿Está seguro de que desea reactivar a este usuario? Al hacerlo, permitirá cualquier intento de inicio de sesión para este usuario.',
reactivate_action: 'Reactivar',
user_reactivated: 'El usuario ha sido reactivado.',
roles: {
name_column: 'Rol',
description_column: 'Descripción',

View file

@ -43,6 +43,16 @@ const user_details = {
"Vous supprimez l'identité existante <nom/>. Etes-vous sûr de vouloir faire ça ?",
},
suspended: 'Suspendu',
suspend_user: "Suspendre l'utilisateur",
suspend_user_reminder:
"Êtes-vous sûr de vouloir suspendre cet utilisateur ? L'utilisateur ne pourra pas se connecter à votre application et ne pourra pas obtenir de nouveau jeton d'accès après l'expiration de celui en cours. En outre, toutes les demandes d'API effectuées par cet utilisateur échoueront.",
suspend_action: 'Suspendre',
user_suspended: "L'utilisateur a été suspendu.",
reactivate_user: "Réactiver l'utilisateur",
reactivate_user_reminder:
'Êtes-vous sûr de vouloir réactiver cet utilisateur ? Cela permettra toute tentative de connexion pour cet utilisateur.',
reactivate_action: 'Réactiver',
user_reactivated: "L'utilisateur a été réactivé.",
roles: {
name_column: 'Rôle',
description_column: 'Description',

View file

@ -43,6 +43,16 @@ const user_details = {
"Stai rimuovendo l'identità esistente <name/>. Sei sicuro di voler procedere?",
},
suspended: 'Sospeso',
suspend_user: 'Sospendi utente',
suspend_user_reminder:
"Sei sicuro di voler sospendere questo utente? L'utente non potrà accedere alla tua app e non sarà in grado di ottenere un nuovo token di accesso dopo la scadenza di quello corrente. Inoltre, qualsiasi richiesta API effettuata da questo utente non avrà esito.",
suspend_action: 'Sospendi',
user_suspended: "L'utente è stato sospeso.",
reactivate_user: 'Riattiva lutente',
reactivate_user_reminder:
'Sei sicuro di voler riattivare questo utente? Ciò consentirà eventuali tentativi di accesso per questo utente.',
reactivate_action: 'Riattiva',
user_reactivated: "L'utente è stato riattivato.",
roles: {
name_column: 'Ruolo',
description_column: 'Descrizione',

View file

@ -41,6 +41,16 @@ const user_details = {
'既存の<name/>アイデンティティを削除しています。本当にそれをやり遂げますか?',
},
suspended: '停止中',
suspend_user: 'ユーザーを一時停止',
suspend_user_reminder:
'ユーザーを一時停止してよろしいですか?この操作を行うと、ユーザーはアプリにサインインできなくなり、現在のアクセストークンが期限切れになった後、新しいアクセストークンを取得できなくなります。さらに、このユーザーによって行われたすべての API リクエストは失敗します。',
suspend_action: '一時停止',
user_suspended: 'ユーザーが停止されました。',
reactivate_user: 'ユーザーをリアクティブ化',
reactivate_user_reminder:
'このユーザーをリアクティブ化することを確認しますか?それにより、このユーザーのサインイン試行が許可されます。',
reactivate_action: '活性化',
user_reactivated: 'ユーザーが再活性化されました。',
roles: {
name_column: '役割',
description_column: '説明',

View file

@ -40,6 +40,16 @@ const user_details = {
deletion_confirmation: '<name/> 신원을 삭제하려고 해요. 정말로 진행할까요?',
},
suspended: '정지됨',
suspend_user: '사용자 정지',
suspend_user_reminder:
'이 사용자를 정지하시겠습니까? 사용자는 앱에 로그인할 수 없으며 현재 액세스 토큰이 만료된 후 새 액세스 토큰을 얻을 수 없게 됩니다. 또한 이 사용자가 수행한 모든 API 요청이 실패합니다.',
suspend_action: '정지',
user_suspended: '사용자가 정지되었습니다.',
reactivate_user: '사용자 재활성화',
reactivate_user_reminder:
'이 사용자를 다시 활성화하시겠습니까? 이렇게 하면이 사용자에 대한 로그인 시도가 허용됩니다.',
reactivate_action: '재활성화',
user_reactivated: '사용자가 재활성화되었습니다.',
roles: {
name_column: '역할',
description_column: '설명',

View file

@ -40,6 +40,16 @@ const user_details = {
deletion_confirmation: 'Usuwasz istniejącą tożsamość <name/>. Czy na pewno chcesz to zrobić?',
},
suspended: 'Zawieszony',
suspend_user: 'Zawieś użytkownika',
suspend_user_reminder:
'Czy na pewno chcesz zawiesić tego użytkownika? Użytkownik nie będzie mógł się zalogować do Twojej aplikacji i nie będzie mógł uzyskać nowego tokena dostępu po wygaśnięciu obecnego. Ponadto, jakiekolwiek żądania API złożone przez tego użytkownika będą nieudane.',
suspend_action: 'Zawieś',
user_suspended: 'Użytkownik został zawieszony.',
reactivate_user: 'Aktywuj użytkownika',
reactivate_user_reminder:
'Czy na pewno chcesz aktywować tego użytkownika? Umożliwi to wszystkie próby logowania dla tego użytkownika.',
reactivate_action: 'Aktywuj',
user_reactivated: 'Użytkownik został aktywowany.',
roles: {
name_column: 'Rola',
description_column: 'Opis',

View file

@ -41,6 +41,16 @@ const user_details = {
'Você está removendo a identidade <name/> existente. Você tem certeza que deseja fazer isso?',
},
suspended: 'Suspenso',
suspend_user: 'Suspender usuário',
suspend_user_reminder:
'Tem certeza de que deseja suspender este usuário? O usuário não poderá entrar em seu aplicativo e não poderá obter um novo token de acesso após a expiração do atual. Além disso, qualquer solicitação de API feita por este usuário falhará.',
suspend_action: 'Suspender',
user_suspended: 'O usuário foi suspenso',
reactivate_user: 'Reativar usuário',
reactivate_user_reminder:
'Tem certeza de que deseja reativar este usuário? Fazendo isso permitirá quaisquer tentativas de login para este usuário.',
reactivate_action: 'Reativar',
user_reactivated: 'O usuário foi reativado',
roles: {
name_column: 'Função',
description_column: 'Descrição',

View file

@ -4,18 +4,18 @@ const user_details = {
created_title: 'Este utilizador foi criado com sucesso',
created_guide: 'Pode enviar as seguintes informações de login para o utilizador',
created_username: 'Utilizador:',
created_password: 'Password:',
created_password: 'Palavra-passe:',
menu_delete: 'eliminar',
delete_description:
'Esta ação não pode ser desfeita. Isso ira eliminar o utilizador permanentemente.',
'Esta ação não pode ser desfeita. Isso irá eliminar o utilizador permanentemente.',
deleted: 'O utilizador foi eliminado com sucesso',
reset_password: {
reset_password: 'Redefinir password',
title: 'Tem a certeza que deseja redefinir a password?',
reset_password: 'Redefinir palavra-passe',
title: 'Tem a certeza que deseja redefinir a palavra-passe?',
content:
'Esta ação não pode ser desfeita. Isso irá redefinir as informações de login do utilizador.',
congratulations: 'Este utilizador foi redefinido',
new_password: 'Nova password:',
new_password: 'Nova palavra-passe:',
},
tab_settings: 'Definições',
tab_roles: 'Funções',
@ -43,6 +43,16 @@ const user_details = {
'Está removendo a identidade <name/> existente. Tem a certeza que deseja fazer isso?',
},
suspended: 'suspenso',
suspend_user: 'Suspender utilizador',
suspend_user_reminder:
'Tem a certeza que deseja suspender este utilizador? O utilizador não conseguira entrar na sua aplicação e não será capaz de obter um novo Token de acesso após o termo do atual. Além disso, qualquer pedido API feito por este utilizador irá falhar.',
suspend_action: 'Suspender',
user_suspended: 'O utilizador foi suspenso.',
reactivate_user: 'Reativar utilizador',
reactivate_user_reminder:
'Tem a certeza que deseja reativar este utilizador? Isso permitirá tentativas de login para este utilizador.',
reactivate_action: 'Reativar',
user_reactivated: 'O utilizador foi reativado.',
roles: {
name_column: 'Função',
description_column: 'Descrição',

View file

@ -41,6 +41,16 @@ const user_details = {
'Вы удаляете существующий идентификатор <name/>. Вы уверены, что хотите это сделать?',
},
suspended: 'Приостановлен',
suspend_user: 'Приостановить пользователя',
suspend_user_reminder:
'Вы уверены, что хотите приостановить этого пользователя? Пользователь не сможет войти в ваше приложение, и он не сможет получить новый токен доступа после истечения срока действия текущего токена. Кроме того, любые API-запросы, сделанные этим пользователем, завершатся неудачей.',
suspend_action: 'Приостановить',
user_suspended: 'Пользователь был приостановлен.',
reactivate_user: 'Возобновить пользователя',
reactivate_user_reminder:
'Вы уверены, что хотите возобновить этого пользователя? Это позволит любые попытки входа в систему для этого пользователя.',
reactivate_action: 'Возобновить',
user_reactivated: 'Пользователь был возобновлен.',
roles: {
name_column: 'Роль',
description_column: 'Описание',

View file

@ -41,6 +41,16 @@ const user_details = {
'Mevcut <name/> kimliğini kaldırıyorsunuz. Bunu yapmak istediğinizden emin misiniz?',
},
suspended: 'Askıya alınmış',
suspend_user: 'Kullanıcıyı Askıya Al',
suspend_user_reminder:
'Bu kullanıcıyı askıya almak istediğinizden emin misiniz? Kullanıcı uygulamanıza giriş yapamayacak ve mevcut erişim belirteci süresi dolduktan sonra yeni bir erişim belirteci alamayacak. Ayrıca bu kullanıcı tarafından yapılan herhangi bir API isteği başarısız olacaktır.',
suspend_action: 'Askıya Al',
user_suspended: 'Kullanıcı askıya alındı.',
reactivate_user: 'Kullanıcıyı Yeniden Etkinleştir',
reactivate_user_reminder:
'Bu kullanıcının yeniden etkinleştirmek istediğinizden emin misiniz? Böyle yapmak, bu kullanıcı için giriş girişimlerine izin verecektir.',
reactivate_action: 'Yeniden Etkinleştir',
user_reactivated: 'Kullanıcı yeniden etkinleştirildi.',
roles: {
name_column: 'Rol',
description_column: 'Açıklama',

View file

@ -39,6 +39,15 @@ const user_details = {
deletion_confirmation: '你在正要删除现有的 <name /> 身份,是否确认?',
},
suspended: '已禁用',
suspend_user: '禁用用户',
suspend_user_reminder:
'确定要禁用该用户吗?该用户将无法登录到你的应用程序,并且在当前访问令牌过期后,将无法获取新的访问令牌。此外,此用户发出的任何 API 请求都将失败。',
suspend_action: '禁用',
user_suspended: '用户已被停用。',
reactivate_user: '重新启用用户',
reactivate_user_reminder: '确定要重新启用该用户吗?这样做将允许该用户的任何登录尝试。',
reactivate_action: '重新启用',
user_reactivated: '用户已重新启用。',
roles: {
name_column: '角色名称',
description_column: '描述',

View file

@ -39,6 +39,15 @@ const user_details = {
deletion_confirmation: '你在正要刪除現有的 <name /> 身份,是否確認?',
},
suspended: '已禁用',
suspend_user: '禁用用户',
suspend_user_reminder:
'你确定要禁用该用户?该用户将无法登录你的应用程序,并且在当前令牌过期后将无法获取新的访问令牌。此外,该用户发出的任何 API 请求都将失败。',
suspend_action: '禁用',
user_suspended: '用戶已被暫時禁用。',
reactivate_user: '重新啓用用户',
reactivate_user_reminder: '你确定要重新啟用該用户?這樣做將允許該用户的任何登錄嘗試。',
reactivate_action: '重新啓用',
user_reactivated: '用戶已重新啟用。',
roles: {
name_column: '角色名稱',
description_column: '描述',

View file

@ -39,6 +39,15 @@ const user_details = {
deletion_confirmation: '你在正要刪除現有的 <name /> 身份,是否確認?',
},
suspended: '已禁用',
suspend_user: '禁用用戶',
suspend_user_reminder:
'你確定要禁用這個用戶嗎?用戶將無法登錄你的應用程式,並且在當前令牌過期後將無法獲取新的訪問令牌。此外,此用戶發出的任何 API 請求都將失敗。',
suspend_action: '禁用',
user_suspended: '用戶已被禁用。',
reactivate_user: '啟用用戶',
reactivate_user_reminder: '你確定要啟用這個用戶嗎?這將允許該用戶的任何登錄嘗試。',
reactivate_action: '啟用',
user_reactivated: '用戶已被啟用。',
roles: {
name_column: '角色名稱',
description_column: '描述',