mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console,phrases): add type selection for role creation modal (#4482)
* feat(console,phrases): add type selection for role creation modal * chore(phrases): update i18n
This commit is contained in:
parent
e8b0b1d020
commit
1863c2f817
21 changed files with 125 additions and 26 deletions
|
@ -1,5 +1,5 @@
|
|||
import type { ResourceResponse, Scope, ScopeResponse } from '@logto/schemas';
|
||||
import { isManagementApi, PredefinedScope } from '@logto/schemas';
|
||||
import { isManagementApi, PredefinedScope, RoleType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import type { ChangeEvent } from 'react';
|
||||
|
@ -10,6 +10,7 @@ import useSWR from 'swr';
|
|||
import Search from '@/assets/icons/search.svg';
|
||||
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
|
||||
import type { DetailedResourceResponse } from '@/components/RoleScopesTransfer/types';
|
||||
import { isProduction } from '@/consts/env';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||
|
@ -20,11 +21,12 @@ import * as styles from './index.module.scss';
|
|||
|
||||
type Props = {
|
||||
roleId?: string;
|
||||
roleType: RoleType;
|
||||
selectedScopes: ScopeResponse[];
|
||||
onChange: (value: ScopeResponse[]) => void;
|
||||
};
|
||||
|
||||
function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
|
||||
function SourceScopesBox({ roleId, roleType, selectedScopes, onChange }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { data: allResources, error: fetchAllResourcesError } = useSWR<
|
||||
|
@ -85,7 +87,10 @@ function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
|
|||
return allResources
|
||||
.filter(
|
||||
({ indicator, scopes }) =>
|
||||
!isManagementApi(indicator) && scopes.some(({ id }) => !excludeScopeIds.has(id))
|
||||
/** Should show management API scopes for machine-to-machine roles */
|
||||
((!isProduction && roleType === RoleType.MachineToMachine) ||
|
||||
!isManagementApi(indicator)) &&
|
||||
scopes.some(({ id }) => !excludeScopeIds.has(id))
|
||||
)
|
||||
.map(({ scopes, ...resource }) => ({
|
||||
...resource,
|
||||
|
@ -96,7 +101,7 @@ function SourceScopesBox({ roleId, selectedScopes, onChange }: Props) {
|
|||
resource,
|
||||
})),
|
||||
}));
|
||||
}, [allResources, roleId, roleScopes]);
|
||||
}, [allResources, roleType, roleId, roleScopes]);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
const lowerCasedKeyword = keyword.toLowerCase();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ScopeResponse } from '@logto/schemas';
|
||||
import type { ScopeResponse, RoleType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import * as transferLayout from '@/scss/transfer.module.scss';
|
||||
|
@ -9,14 +9,20 @@ import * as styles from './index.module.scss';
|
|||
|
||||
type Props = {
|
||||
roleId?: string;
|
||||
roleType: RoleType;
|
||||
value: ScopeResponse[];
|
||||
onChange: (value: ScopeResponse[]) => void;
|
||||
};
|
||||
|
||||
function RoleScopesTransfer({ roleId, value, onChange }: Props) {
|
||||
function RoleScopesTransfer({ roleId, roleType, value, onChange }: Props) {
|
||||
return (
|
||||
<div className={classNames(transferLayout.container, styles.roleScopesTransfer)}>
|
||||
<SourceScopesBox roleId={roleId} selectedScopes={value} onChange={onChange} />
|
||||
<SourceScopesBox
|
||||
roleId={roleId}
|
||||
roleType={roleType}
|
||||
selectedScopes={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<div className={transferLayout.verticalBar} />
|
||||
<TargetScopesBox selectedScopes={value} onChange={onChange} />
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ScopeResponse } from '@logto/schemas';
|
||||
import type { ScopeResponse, RoleType } from '@logto/schemas';
|
||||
import { useContext, useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
@ -19,11 +19,12 @@ import { hasReachedQuotaLimit } from '@/utils/quota';
|
|||
|
||||
type Props = {
|
||||
roleId: string;
|
||||
roleType: RoleType;
|
||||
totalRoleScopeCount: number;
|
||||
onClose: (success?: boolean) => void;
|
||||
};
|
||||
|
||||
function AssignPermissionsModal({ roleId, totalRoleScopeCount, onClose }: Props) {
|
||||
function AssignPermissionsModal({ roleId, roleType, totalRoleScopeCount, onClose }: Props) {
|
||||
const { currentTenantId } = useContext(TenantsContext);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
|
||||
|
@ -108,6 +109,7 @@ function AssignPermissionsModal({ roleId, totalRoleScopeCount, onClose }: Props)
|
|||
<FormField title="role_details.permission.assign_form_field">
|
||||
<RoleScopesTransfer
|
||||
roleId={roleId}
|
||||
roleType={roleType}
|
||||
value={scopes}
|
||||
onChange={(scopes) => {
|
||||
setScopes(scopes);
|
||||
|
|
|
@ -22,7 +22,7 @@ const pageSize = defaultPageSize;
|
|||
|
||||
function RolePermissions() {
|
||||
const {
|
||||
role: { id: roleId },
|
||||
role: { id: roleId, type: roleType },
|
||||
} = useOutletContext<RoleDetailsOutletContext>();
|
||||
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -117,6 +117,7 @@ function RolePermissions() {
|
|||
{isAssignPermissionsModalOpen && totalCount !== undefined && (
|
||||
<AssignPermissionsModal
|
||||
roleId={roleId}
|
||||
roleType={roleType}
|
||||
totalRoleScopeCount={totalCount}
|
||||
onClose={(success) => {
|
||||
if (success) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.roleTypes {
|
||||
display: flex;
|
||||
gap: _.unit(6);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import type { Role, ScopeResponse } from '@logto/schemas';
|
||||
import { internalRolePrefix } from '@logto/schemas';
|
||||
import { RoleType, internalRolePrefix } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useContext } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
@ -9,26 +10,36 @@ import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
|
|||
import PlanName from '@/components/PlanName';
|
||||
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
|
||||
import RoleScopesTransfer from '@/components/RoleScopesTransfer';
|
||||
import { isProduction } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import Button from '@/ds-components/Button';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
import { hasReachedQuotaLimit } from '@/utils/quota';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const radioOptions: Array<{ key: AdminConsoleKey; value: RoleType }> = [
|
||||
{ key: 'roles.type_user', value: RoleType.User },
|
||||
{ key: 'roles.type_machine_to_machine', value: RoleType.MachineToMachine },
|
||||
];
|
||||
|
||||
export type Props = {
|
||||
totalRoleCount: number;
|
||||
onClose: (createdRole?: Role) => void;
|
||||
};
|
||||
|
||||
type CreateRoleFormData = Pick<Role, 'name' | 'description'> & {
|
||||
type CreateRoleFormData = Pick<Role, 'name' | 'description' | 'type'> & {
|
||||
scopes: ScopeResponse[];
|
||||
};
|
||||
|
||||
type CreateRolePayload = Pick<Role, 'name' | 'description'> & {
|
||||
type CreateRolePayload = Pick<Role, 'name' | 'description' | 'type'> & {
|
||||
scopeIds?: string[];
|
||||
};
|
||||
|
||||
|
@ -42,13 +53,13 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
register,
|
||||
watch,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm<CreateRoleFormData>();
|
||||
} = useForm<CreateRoleFormData>({ defaultValues: { type: RoleType.User } });
|
||||
|
||||
const api = useApi();
|
||||
const roleScopes = watch('scopes', []);
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
trySubmitSafe(async ({ name, description, scopes }) => {
|
||||
trySubmitSafe(async ({ name, description, type, scopes }) => {
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
@ -56,6 +67,7 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
const payload: CreateRolePayload = {
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
scopeIds: conditional(scopes.length > 0 && scopes.map(({ id }) => id)),
|
||||
};
|
||||
|
||||
|
@ -146,6 +158,28 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
error={errors.name?.message}
|
||||
/>
|
||||
</FormField>
|
||||
{!isProduction && (
|
||||
<FormField title="roles.role_type">
|
||||
<Controller
|
||||
name="type"
|
||||
control={control}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<RadioGroup
|
||||
name={name}
|
||||
className={styles.roleTypes}
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
}}
|
||||
>
|
||||
{radioOptions.map(({ key, value }) => (
|
||||
<Radio key={value} title={<DynamicT forKey={key} />} value={value} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField isRequired title="roles.role_description">
|
||||
<TextInput
|
||||
{...register('description', { required: true })}
|
||||
|
@ -159,7 +193,7 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
name="scopes"
|
||||
defaultValue={[]}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<RoleScopesTransfer value={value} onChange={onChange} />
|
||||
<RoleScopesTransfer roleType={watch('type')} value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Rollen beinhalten Berechtigungen, die bestimmen, was ein Benutzer tun kann. RBAC verwendet Rollen, um Benutzern Zugriff auf Ressourcen für bestimmte Aktionen zu geben.',
|
||||
create: 'Rolle erstellen',
|
||||
role_name: 'Rolle',
|
||||
role_name: 'Rollenname',
|
||||
role_type: 'Rollenart',
|
||||
type_user: 'Benutzerrolle',
|
||||
type_machine_to_machine: 'Maschinen-zu-Maschinen-App-Rolle',
|
||||
role_description: 'Beschreibung',
|
||||
role_name_placeholder: 'Geben Sie Ihren Rollennamen ein',
|
||||
role_description_placeholder: 'Geben Sie Ihre Rollenbeschreibung ein',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Roles include permissions that determine what a user can do. RBAC uses roles to give users access to resources for specific actions.',
|
||||
create: 'Create Role',
|
||||
role_name: 'Role',
|
||||
role_name: 'Role name',
|
||||
role_type: 'Role type',
|
||||
type_user: 'User role',
|
||||
type_machine_to_machine: 'Machine-to-machine app role',
|
||||
role_description: 'Description',
|
||||
role_name_placeholder: 'Enter your role name',
|
||||
role_description_placeholder: 'Enter your role description',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Los roles incluyen permisos que determinan lo que un usuario puede hacer. RBAC utiliza roles para dar acceso a recursos a los usuarios para acciones específicas.',
|
||||
create: 'Crear Rol',
|
||||
role_name: 'Rol',
|
||||
role_name: 'Nombre de rol',
|
||||
role_type: 'Tipo de rol',
|
||||
type_user: 'Rol de usuario',
|
||||
type_machine_to_machine: 'Rol de aplicación de máquina a máquina',
|
||||
role_description: 'Descripción',
|
||||
role_name_placeholder: 'Ingrese el nombre de su rol',
|
||||
role_description_placeholder: 'Ingrese la descripción de su rol',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
"Les rôles incluent des autorisations qui déterminent ce qu'un utilisateur peut faire. RBAC utilise des rôles pour donner aux utilisateurs accès à des ressources pour des actions spécifiques.",
|
||||
create: 'Créer un rôle',
|
||||
role_name: 'Rôle',
|
||||
role_name: 'Nom du rôle',
|
||||
role_type: 'Type de rôle',
|
||||
type_user: 'Rôle utilisateur',
|
||||
type_machine_to_machine: "Rôle d'application machine-à-machine",
|
||||
role_description: 'Description',
|
||||
role_name_placeholder: 'Entrez le nom de votre rôle',
|
||||
role_description_placeholder: 'Entrez la description de votre rôle',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
"I ruoli includono le autorizzazioni che determinano ciò che un utente può fare. RBAC utilizza i ruoli per dare agli utenti l'accesso alle risorse necessarie per specifiche azioni.",
|
||||
create: 'Crea Ruolo',
|
||||
role_name: 'Ruolo',
|
||||
role_name: 'Nome ruolo',
|
||||
role_type: 'Tipo ruolo',
|
||||
type_user: 'Ruolo utente',
|
||||
type_machine_to_machine: 'Ruolo app M2M',
|
||||
role_description: 'Descrizione',
|
||||
role_name_placeholder: 'Inserisci il nome del tuo ruolo',
|
||||
role_description_placeholder: 'Inserisci la descrizione del tuo ruolo',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'ロールには、ユーザーが実行できるアクションを決定する権限が含まれます。RBACは、特定のアクションのためにリソースにアクセスするためにユーザーに権限を付与するために、ロールを使用します。',
|
||||
create: 'ロールを作成する',
|
||||
role_name: 'ロールの名前',
|
||||
role_name: '役割名',
|
||||
role_type: '役割タイプ',
|
||||
type_user: 'ユーザーの役割',
|
||||
type_machine_to_machine: 'マシン対マシンアプリの役割',
|
||||
role_description: '説明',
|
||||
role_name_placeholder: 'ロールの名前を入力してください',
|
||||
role_description_placeholder: 'ロールの説明を入力してください',
|
||||
|
|
|
@ -5,6 +5,9 @@ const roles = {
|
|||
'역할은 사용자가 무엇을 할 수 있는지를 결정하는 권한을 포함해요. RBAC는 사용자에게 특정 행동에 대한 접근 권한을 부여하기 위해 역할을 사용해요.',
|
||||
create: '역할 생성',
|
||||
role_name: '역할 이름',
|
||||
role_type: '역할 유형',
|
||||
type_user: '사용자 역할',
|
||||
type_machine_to_machine: '기계 간 앱 역할',
|
||||
role_description: '설명',
|
||||
role_name_placeholder: '역할 이름을 입력하세요',
|
||||
role_description_placeholder: '역할 설명을 입력하세요',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Role zawiera uprawnienia określające, co użytkownik może robić. RBAC wykorzystuje role do udostępniania użytkownikom zasobów do określonych działań.',
|
||||
create: 'Utwórz rolę',
|
||||
role_name: 'Rola',
|
||||
role_name: 'Nazwa roli',
|
||||
role_type: 'Typ roli',
|
||||
type_user: 'Rola użytkownika',
|
||||
type_machine_to_machine: 'Rola aplikacji Machine-to-Machine',
|
||||
role_description: 'Opis',
|
||||
role_name_placeholder: 'Wprowadź nazwę swojej roli',
|
||||
role_description_placeholder: 'Wprowadź opis swojej roli',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'As funções incluem permissões que determinam o que um usuário pode fazer. O RBAC usa funções para dar aos usuários acesso a recursos para ações específicas.',
|
||||
create: 'Criar função',
|
||||
role_name: 'Função',
|
||||
role_name: 'Nome da função',
|
||||
role_type: 'Tipo de função',
|
||||
type_user: 'Função do usuário',
|
||||
type_machine_to_machine: 'Função do aplicativo de máquina para máquina',
|
||||
role_description: 'Descrição',
|
||||
role_name_placeholder: 'Insira o nome da sua função',
|
||||
role_description_placeholder: 'Insira a descrição da sua função',
|
||||
|
|
|
@ -5,6 +5,9 @@ const roles = {
|
|||
'Os papéis incluem permissões que determinam o que um usuário pode fazer. RBAC usa papéis para conceder acesso a recursos para ações específicas.',
|
||||
create: 'Criar papel',
|
||||
role_name: 'Nome do papel',
|
||||
role_type: 'Tipo de papel',
|
||||
type_user: 'Função de usuário',
|
||||
type_machine_to_machine: 'Função de aplicação máquina-a-máquina',
|
||||
role_description: 'Descrição',
|
||||
role_name_placeholder: 'Digite o nome do papel',
|
||||
role_description_placeholder: 'Digite a descrição do papel',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Роли включают права доступа, которые определяют, что может делать пользователь. RBAC использует роли для предоставления пользователям доступа к ресурсам для конкретных действий.',
|
||||
create: 'Создать роль',
|
||||
role_name: 'Роль',
|
||||
role_name: 'Имя роли',
|
||||
role_type: 'Тип роли',
|
||||
type_user: 'Роль пользователя',
|
||||
type_machine_to_machine: 'Роль приложения между машинами',
|
||||
role_description: 'Описание',
|
||||
role_name_placeholder: 'Введите название роли',
|
||||
role_description_placeholder: 'Введите описание роли',
|
||||
|
|
|
@ -4,7 +4,10 @@ const roles = {
|
|||
subtitle:
|
||||
'Roller, bir kullanıcının ne yapabileceğini belirleyen izinleri içerir. RBAC, kullanıcılara belirli işlemler için kaynaklara erişim vermek için roller kullanır.',
|
||||
create: 'Rol Oluştur',
|
||||
role_name: 'Rol Adı',
|
||||
role_name: 'Rol adı',
|
||||
role_type: 'Rol tipi',
|
||||
type_user: 'Kullanıcı rolü',
|
||||
type_machine_to_machine: 'Makine-makine uygulama rolü',
|
||||
role_description: 'Açıklama',
|
||||
role_name_placeholder: 'Rol adınızı girin',
|
||||
role_description_placeholder: 'Rol açıklamanızı girin',
|
||||
|
|
|
@ -5,6 +5,9 @@ const roles = {
|
|||
'RBAC 是一种访问控制方法,它使用角色来决定用户可以做什么事情,包括授予用户访问特定资源的权限。',
|
||||
create: '创建角色',
|
||||
role_name: '角色名称',
|
||||
role_type: '角色类型',
|
||||
type_user: '用户角色',
|
||||
type_machine_to_machine: '机器对机器应用程序角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '输入你的角色名称',
|
||||
role_description_placeholder: '输入你的角色描述',
|
||||
|
|
|
@ -5,6 +5,9 @@ const roles = {
|
|||
'RBAC 是一種訪問控制方法,它使用角色來決定用戶可以做什麼事情,包括授予用戶訪問特定資源的權限。',
|
||||
create: '創建角色',
|
||||
role_name: '角色名稱',
|
||||
role_type: '角色類型',
|
||||
type_user: '用戶角色',
|
||||
type_machine_to_machine: '機器到機器應用程式角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '輸入你的角色名稱',
|
||||
role_description_placeholder: '輸入你的角色描述',
|
||||
|
|
|
@ -5,6 +5,9 @@ const roles = {
|
|||
'RBAC 是一種訪問控制方法,它使用角色來決定用戶可以做什麼事情,包括授予用戶訪問特定資源的權限。',
|
||||
create: '建立角色',
|
||||
role_name: '角色名稱',
|
||||
role_type: '角色類型',
|
||||
type_user: '使用者角色',
|
||||
type_machine_to_machine: '機器對機器應用角色',
|
||||
role_description: '描述',
|
||||
role_name_placeholder: '輸入你的角色名稱',
|
||||
role_description_placeholder: '輸入你的角色描述',
|
||||
|
|
Loading…
Reference in a new issue