diff --git a/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx b/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx index 584dcf7c2..817fc725d 100644 --- a/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx +++ b/packages/console/src/components/RoleScopesTransfer/components/SourceScopesBox/index.tsx @@ -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(); diff --git a/packages/console/src/components/RoleScopesTransfer/index.tsx b/packages/console/src/components/RoleScopesTransfer/index.tsx index 14f408ad2..9368422fb 100644 --- a/packages/console/src/components/RoleScopesTransfer/index.tsx +++ b/packages/console/src/components/RoleScopesTransfer/index.tsx @@ -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 (
- +
diff --git a/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx b/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx index 6f6704753..c8b3e63a9 100644 --- a/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx +++ b/packages/console/src/pages/RoleDetails/RolePermissions/components/AssignPermissionsModal/index.tsx @@ -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) { setScopes(scopes); diff --git a/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx b/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx index 94b87242a..07423446b 100644 --- a/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx +++ b/packages/console/src/pages/RoleDetails/RolePermissions/index.tsx @@ -22,7 +22,7 @@ const pageSize = defaultPageSize; function RolePermissions() { const { - role: { id: roleId }, + role: { id: roleId, type: roleType }, } = useOutletContext(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); @@ -117,6 +117,7 @@ function RolePermissions() { {isAssignPermissionsModalOpen && totalCount !== undefined && ( { if (success) { diff --git a/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss b/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss new file mode 100644 index 000000000..2aaef3ad4 --- /dev/null +++ b/packages/console/src/pages/Roles/components/CreateRoleForm/index.module.scss @@ -0,0 +1,6 @@ +@use '@/scss/underscore' as _; + +.roleTypes { + display: flex; + gap: _.unit(6); +} diff --git a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx index 9cb8e67f6..b504e2bea 100644 --- a/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx +++ b/packages/console/src/pages/Roles/components/CreateRoleForm/index.tsx @@ -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 & { +type CreateRoleFormData = Pick & { scopes: ScopeResponse[]; }; -type CreateRolePayload = Pick & { +type CreateRolePayload = Pick & { scopeIds?: string[]; }; @@ -42,13 +53,13 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) { register, watch, formState: { isSubmitting, errors }, - } = useForm(); + } = useForm({ 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} /> + {!isProduction && ( + + ( + { + onChange(value); + }} + > + {radioOptions.map(({ key, value }) => ( + } value={value} /> + ))} + + )} + /> + + )} ( - + )} /> diff --git a/packages/phrases/src/locales/de/translation/admin-console/roles.ts b/packages/phrases/src/locales/de/translation/admin-console/roles.ts index cd2b00030..f55129ea6 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/en/translation/admin-console/roles.ts b/packages/phrases/src/locales/en/translation/admin-console/roles.ts index 0a02fcba9..d6e0cef49 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/es/translation/admin-console/roles.ts b/packages/phrases/src/locales/es/translation/admin-console/roles.ts index 02dbff0cf..42a9a33f7 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts index 9c2edc018..c4c37983f 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/it/translation/admin-console/roles.ts b/packages/phrases/src/locales/it/translation/admin-console/roles.ts index a78c2f2ef..983954c55 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts index 2369aa0b2..ea367782e 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/roles.ts @@ -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: 'ロールの説明を入力してください', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts index 0c3ca68b1..4897a9ec5 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/roles.ts @@ -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: '역할 설명을 입력하세요', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts index 5601bf163..312ddb1c5 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts index 193365f91..eeaf5c83b 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts index c39893409..630e443f2 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts index b1fc1d8c3..6b00fa3f5 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/roles.ts @@ -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: 'Введите описание роли', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts index b2b8f9907..f9ab700a7 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/roles.ts @@ -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', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts index 0352c1d78..4acd00b42 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/roles.ts @@ -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: '输入你的角色描述', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts index 50cb8f552..63438de60 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/roles.ts @@ -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: '輸入你的角色描述', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts index 61b7f83a8..7a5607a2a 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/roles.ts @@ -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: '輸入你的角色描述',