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

feat(console,phrases): add application permissions edit modal (#5239)

* feat(console,phrases): implement permission edit modal

implement permission edit modal

* fix(console): remove useless undefined type

remove useless undefined type
This commit is contained in:
simeng-li 2024-01-22 11:33:00 +08:00 committed by GitHub
parent 3cd14ba75d
commit e539d999f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 322 additions and 15 deletions

View file

@ -0,0 +1,135 @@
import { ApplicationUserConsentScopeType } from '@logto/schemas';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import Button from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw';
import FormField from '@/ds-components/FormField';
import ModalLayout from '@/ds-components/ModalLayout';
import TextInput from '@/ds-components/TextInput';
import useActionTranslation from '@/hooks/use-action-translation';
import useApi from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss';
import { trySubmitSafe } from '@/utils/form';
import { type ScopesTableRowDataType, type UserScopeTableRowDataType } from '../use-scopes-table';
/**
* ApplicationScopesManagementModal
*
* This modal is used to edit the resource and organization scopes' descriptions.
*/
export type EditableScopeData = Exclude<ScopesTableRowDataType, UserScopeTableRowDataType>;
type Props = {
scope?: EditableScopeData;
onClose: () => void;
onSubmit: (scope: EditableScopeData) => void;
};
function ApplicationScopesManagementModal({ scope, onClose, onSubmit }: Props) {
const api = useApi();
const {
reset,
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<EditableScopeData>();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const tAction = useActionTranslation();
const onSubmitHandler = handleSubmit(trySubmitSafe(onSubmit));
// Reset form on open
useEffect(() => {
if (scope) {
reset(scope);
}
}, [reset, scope]);
return (
<ReactModal
isOpen={Boolean(scope)}
className={modalStyles.content}
overlayClassName={modalStyles.overlay}
onRequestClose={onClose}
>
{scope && (
<ModalLayout
title={
scope.type === ApplicationUserConsentScopeType.ResourceScopes ? (
'api_resource_details.permission.edit_title'
) : (
<DangerousRaw>
{tAction('edit', 'organizations.organization_permission')}
</DangerousRaw>
)
}
subtitle={
scope.type === ApplicationUserConsentScopeType.ResourceScopes ? (
<DangerousRaw>
{t('api_resource_details.permission.edit_subtitle', {
resourceName: scope.resourceName,
})}
</DangerousRaw>
) : undefined
}
learnMoreLink={
scope.type === ApplicationUserConsentScopeType.ResourceScopes
? {
href: 'https://docs.logto.io/docs/recipes/rbac/manage-permissions-and-roles#manage-role-permissions',
targetBlank: 'noopener',
}
: undefined
}
footer={
<Button
isLoading={isSubmitting}
htmlType="submit"
type="primary"
size="large"
title="general.save"
onClick={onSubmitHandler}
/>
}
onClose={onClose}
>
<form>
<FormField isRequired title="general.name">
<TextInput
disabled
error={Boolean(errors.name)}
{...register('name', { required: true })}
/>
</FormField>
<FormField
title="general.description"
isRequired={scope.type === ApplicationUserConsentScopeType.ResourceScopes}
tip={t('application_details.permissions.permission_description_tips')}
>
<TextInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
placeholder={
scope.type === ApplicationUserConsentScopeType.ResourceScopes
? t('api_resource_details.permission.description_placeholder')
: t('organizations.create_permission_placeholder')
}
error={Boolean(errors.description)}
{...register('description', {
required: scope.type === ApplicationUserConsentScopeType.ResourceScopes,
})}
/>
</FormField>
</form>
</ModalLayout>
)}
</ReactModal>
);
}
export default ApplicationScopesManagementModal;

View file

@ -1,4 +1,8 @@
import { type Application, type ApplicationUserConsentScopesResponse } from '@logto/schemas';
import {
type Application,
type ApplicationUserConsentScopesResponse,
ApplicationUserConsentScopeType,
} from '@logto/schemas';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
@ -11,6 +15,9 @@ import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import ApplicationScopesAssignmentModal from './ApplicationScopesAssignmentModal';
import ApplicationScopesManagementModal, {
type EditableScopeData,
} from './ApplicationScopesManagementModal';
import * as styles from './index.module.scss';
import useScopesTable from './use-scopes-table';
@ -22,7 +29,9 @@ function Permissions({ application }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isAssignScopesModalOpen, setIsAssignScopesModalOpen] = useState(false);
const { parseRowGroup, deleteScope } = useScopesTable();
const [editScopeModalData, setEditScopeModalData] = useState<EditableScopeData>();
const { parseRowGroup, deleteScope, editScope } = useScopesTable();
const { data, error, mutate, isLoading } = useSWR<
ApplicationUserConsentScopesResponse,
@ -74,9 +83,14 @@ function Permissions({ application }: Props) {
delete: 'application_details.permissions.delete_text',
deleteConfirmation: 'general.remove',
}}
onEdit={() => {
// TODO: Implement edit permission
}}
onEdit={
// UserScopes is not editable
data.type === ApplicationUserConsentScopeType.UserScopes
? undefined
: () => {
setEditScopeModalData(data);
}
}
onDelete={async () => {
await deleteScope(data, application.id);
void mutate();
@ -100,6 +114,19 @@ function Permissions({ application }: Props) {
}}
/>
)}
{data && (
<ApplicationScopesManagementModal
scope={editScopeModalData}
onClose={() => {
setEditScopeModalData(undefined);
}}
onSubmit={async (scope) => {
await editScope(scope);
void mutate();
setEditScopeModalData(undefined);
}}
/>
)}
</>
);
}

View file

@ -9,13 +9,29 @@ import useApi from '@/hooks/use-api';
import * as styles from './index.module.scss';
type ScopesTableRowDataType = {
type: ApplicationUserConsentScopeType;
export type UserScopeTableRowDataType = {
type: ApplicationUserConsentScopeType.UserScopes;
id: string;
name: string;
description?: string;
};
type OrganizationScopeTableRowDataType = {
type: ApplicationUserConsentScopeType.OrganizationScopes;
} & ApplicationUserConsentScopesResponse['organizationScopes'][number];
type ResourceScopeTableRowDataType = {
type: ApplicationUserConsentScopeType.ResourceScopes;
// Resource ID is required for resource scope patch request
resourceId: string;
resourceName: string;
} & ApplicationUserConsentScopesResponse['resourceScopes'][number]['scopes'][number];
export type ScopesTableRowDataType =
| UserScopeTableRowDataType
| OrganizationScopeTableRowDataType
| ResourceScopeTableRowDataType;
type ScopesTableRowGroupType = {
key: string;
label: string;
@ -54,11 +70,9 @@ const useScopesTable = () => {
key: ApplicationUserConsentScopeType.OrganizationScopes,
label: t('application_details.permissions.organization_permissions'),
labelRowClassName: styles.sectionTitleRow,
data: organizationScopes.map(({ id, name, description }) => ({
data: organizationScopes.map((scope) => ({
type: ApplicationUserConsentScopeType.OrganizationScopes,
id,
name,
description: description ?? undefined,
...scope,
})),
};
@ -67,11 +81,11 @@ const useScopesTable = () => {
key: resource.indicator,
label: resource.name,
labelRowClassName: styles.sectionTitleRow,
data: scopes.map(({ id, name, description }) => ({
data: scopes.map((scope) => ({
type: ApplicationUserConsentScopeType.ResourceScopes,
id,
name,
description,
...scope,
resourceId: resource.id,
resourceName: resource.name,
})),
})
);
@ -87,9 +101,38 @@ const useScopesTable = () => {
[api]
);
// Only description is editable
const editScope = useCallback(
async (scope: ScopesTableRowDataType) => {
const { type, id, description } = scope;
if (type === ApplicationUserConsentScopeType.ResourceScopes) {
const { resourceId } = scope;
await api.patch(`api/resources/${resourceId}/scopes/${id}`, {
json: {
description,
},
});
return;
}
if (type === ApplicationUserConsentScopeType.OrganizationScopes) {
await api.patch(`api/organization-scopes/${id}`, {
json: {
description,
},
});
}
},
[api]
);
return {
parseRowGroup,
deleteScope,
editScope,
};
};

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Berechtigung erstellen',
create_subtitle: 'Definieren Sie die benötigten Berechtigungen (Bereiche) für diese API.',
confirm_create: 'Berechtigung erstellen',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Berechtigungsname',
name_placeholder: 'read:resource',
forbidden_space_in_name: 'Der Berechtigungsname darf keine Leerzeichen enthalten.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Rolle',

View file

@ -21,6 +21,8 @@ const api_resource_details = {
create_title: 'Create permission',
create_subtitle: 'Define the permissions (scopes) needed by this API.',
confirm_create: 'Create permission',
edit_title: 'Edit API permission',
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Permission name',
name_placeholder: 'read:resource',
forbidden_space_in_name: 'The permission name must not contain any spaces.',

View file

@ -117,6 +117,8 @@ const application_details = {
user_permissions_assignment_form_title: 'Add the user profile permissions',
organization_permissions_assignment_form_title: 'Add the organization permissions',
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Role',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Crear permiso',
create_subtitle: 'Define los permisos (scopes) necesarios para esta API.',
confirm_create: 'Crear permiso',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Nombre del permiso',
name_placeholder: 'leer:recurso',
forbidden_space_in_name: 'El nombre del permiso no debe contener espacios.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Rol',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Créer une autorisation',
create_subtitle: 'Définir les autorisations (scopes) requises pour cette API.',
confirm_create: 'Créer une autorisation',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: "Nom de l'autorisation",
name_placeholder: 'lecture:ressource',
forbidden_space_in_name: "Le nom de l'autorisation ne doit pas contenir d'espaces.",

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Rôle',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'crea autorizzazione',
create_subtitle: 'Definire le autorizzazioni (ambiti) necessarie per questa API.',
confirm_create: 'Crea autorizzazione',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Nome autorizzazione',
name_placeholder: 'lettura:risorsa',
forbidden_space_in_name: "Il nome dell'autorizzazione non deve contenere spazi.",

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Ruolo',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: '権限の作成',
create_subtitle: 'このAPIで必要な権限スコープを定義します。',
confirm_create: '権限を作成',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: '権限名',
name_placeholder: 'read:resource',
forbidden_space_in_name: '権限名にはスペースを含めることはできません。',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: '役割',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: '권한 생성',
create_subtitle: '이 API에 필요한 권한을 정의합니다.',
confirm_create: '권한 생성',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: '권한 이름',
name_placeholder: 'read:resource',
forbidden_space_in_name: '권한 이름에는 공백을 포함할 수 없습니다.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: '역할',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Utwórz uprawnienie',
create_subtitle: 'Zdefiniuj uprawnienia (zakresy) wymagane przez to API.',
confirm_create: 'Utwórz uprawnienie',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Nazwa uprawnienia',
name_placeholder: 'ready:resource',
forbidden_space_in_name: 'Nazwa uprawnienia nie może zawierać spacji.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Role',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Criar permissão',
create_subtitle: 'Define as permissões (escopos) necessárias para esta API.',
confirm_create: 'Criar permissão',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Nome da permissão',
name_placeholder: 'ler:recurso',
forbidden_space_in_name: 'O nome da permissão não deve conter espaços.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Função',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Criar permissão',
create_subtitle: 'Define as permissões (escopos) necessários para essa API.',
confirm_create: 'Criar permissão',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Nome da permissão',
name_placeholder: 'leitura:recurso',
forbidden_space_in_name: 'O nome da permissão não pode conter espaços.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Nome da função',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'Создать разрешение',
create_subtitle: 'Определите необходимые разрешения (области) для этого API.',
confirm_create: 'Создать разрешение',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'Название разрешения',
name_placeholder: 'read:resource',
forbidden_space_in_name: 'Название разрешения не должно содержать пробелов.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Роль',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: 'İzin Oluştur',
create_subtitle: 'Bu API tarafından gerektirilen izinleri (kapsamları) tanımlayın.',
confirm_create: 'İzin Oluştur',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: 'İzin adı',
name_placeholder: 'read:kaynak',
forbidden_space_in_name: 'İzin adı boşluk içermemelidir.',

View file

@ -166,6 +166,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: 'Rol',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: '创建权限',
create_subtitle: '定义此 API 所需的权限 (scope)。',
confirm_create: '创建权限',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: '权限名称',
name_placeholder: 'read:resource',
forbidden_space_in_name: '权限名称不能包含空格。',

View file

@ -163,6 +163,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: '角色',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: '創建權限',
create_subtitle: '定義此 API 所需的權限 (scope)。',
confirm_create: '創建權限',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: '權限名稱',
name_placeholder: 'read:resource',
forbidden_space_in_name: '權限名稱不能包含空格。',

View file

@ -163,6 +163,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: '角色',

View file

@ -21,6 +21,10 @@ const api_resource_details = {
create_title: '建立權限',
create_subtitle: '定義此 API 所需的權限 (scope)。',
confirm_create: '建立權限',
/** UNTRANSLATED */
edit_title: 'Edit API permission',
/** UNTRANSLATED */
edit_subtitle: 'Define the permissions (scopes) needed by the {{resourceName}} API.',
name: '權限名稱',
name_placeholder: 'read:resource',
forbidden_space_in_name: '權限名稱不能包含空格。',

View file

@ -164,6 +164,9 @@ const application_details = {
organization_permissions_assignment_form_title: 'Add the organization permissions',
/** UNTRANSLATED */
api_resource_permissions_assignment_form_title: 'Add the API resource permissions',
/** UNTRANSLATED */
permission_description_tips:
'When Logto is used as an Identity Provider (IdP) for authentication in third-party apps, and users are asked for authorization, this description appears on the consent screen.',
},
roles: {
name_column: '角色',