From 352ca031777a404bb4a32b6c2901b72459604fb9 Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Thu, 21 Sep 2023 16:54:17 +0800 Subject: [PATCH] refactor(console,phrases,core): add roles and logs tabs for m2m app details page (#4548) --- .../src/components/AuditLogTable/index.tsx | 48 +++-- .../SourceRoleItem/index.module.scss | 0 .../components/SourceRoleItem/index.tsx | 0 .../SourceRolesBox/index.module.scss | 0 .../components/SourceRolesBox/index.tsx | 19 +- .../TargetRoleItem/index.module.scss | 0 .../components/TargetRoleItem/index.tsx | 0 .../TargetRolesBox/index.module.scss | 0 .../components/TargetRolesBox/index.tsx | 0 .../index.module.scss | 0 .../index.tsx | 11 +- packages/console/src/consts/page-tabs.ts | 2 + .../components/MachineLogs/index.module.scss | 7 + .../components/MachineLogs/index.tsx | 11 ++ .../index.module.scss | 26 +++ .../index.tsx | 183 ++++++++++++++++++ .../src/pages/ApplicationDetails/index.tsx | 28 +++ .../components/AssignToRoleModal}/index.tsx | 64 ++++-- .../src/pages/UserDetails/UserRoles/index.tsx | 8 +- packages/core/src/queries/roles.ts | 13 +- packages/core/src/routes/role.ts | 18 +- packages/integration-tests/src/api/role.ts | 5 +- .../src/tests/api/role.application.test.ts | 6 +- .../src/tests/api/role.user.test.ts | 13 +- .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ .../admin-console/application-details.ts | 37 ++++ 39 files changed, 961 insertions(+), 56 deletions(-) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/SourceRoleItem/index.module.scss (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/SourceRoleItem/index.tsx (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/SourceRolesBox/index.module.scss (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/SourceRolesBox/index.tsx (85%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/TargetRoleItem/index.module.scss (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/TargetRoleItem/index.tsx (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/TargetRolesBox/index.module.scss (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/components/TargetRolesBox/index.tsx (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/index.module.scss (100%) rename packages/console/src/components/{UserRolesTransfer => RolesTransfer}/index.tsx (66%) create mode 100644 packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.module.scss create mode 100644 packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.tsx create mode 100644 packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.module.scss create mode 100644 packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.tsx rename packages/console/src/pages/{UserDetails/UserRoles/components/AssignRolesModal => Roles/components/AssignToRoleModal}/index.tsx (51%) diff --git a/packages/console/src/components/AuditLogTable/index.tsx b/packages/console/src/components/AuditLogTable/index.tsx index b4b681088..f5f2d54e1 100644 --- a/packages/console/src/components/AuditLogTable/index.tsx +++ b/packages/console/src/components/AuditLogTable/index.tsx @@ -1,5 +1,5 @@ -import type { Log } from '@logto/schemas'; -import { LogResult } from '@logto/schemas'; +import type { Log, ApplicationResponse } from '@logto/schemas'; +import { LogResult, ApplicationType } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import { useTranslation } from 'react-i18next'; import useSWR from 'swr'; @@ -27,24 +27,33 @@ const auditLogEventOptions = Object.entries(auditLogEventTitle).map(([value, tit })); type Props = { + applicationId?: string; userId?: string; className?: string; }; -function AuditLogTable({ userId, className }: Props) { +function AuditLogTable({ applicationId, userId, className }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const pageSize = defaultPageSize; - const [{ page, event, applicationId }, updateSearchParameters] = useSearchParametersWatcher({ - page: 1, - event: '', - applicationId: '', - }); + + const [{ page, event, applicationId: applicationIdFromSearch }, updateSearchParameters] = + useSearchParametersWatcher({ + page: 1, + event: '', + ...conditional(applicationId && { applicationId: '' }), + }); + + // TODO: LOG-7135, revisit this fallback logic and see whether this should be done outside of this component. + const searchApplicationId = applicationId ?? applicationIdFromSearch; + const { data: specifiedApplication } = useSWR( + applicationId && `api/applications/${applicationId}` + ); const url = buildUrl('api/logs', { page: String(page), page_size: String(pageSize), ...conditional(event && { logKey: event }), - ...conditional(applicationId && { applicationId }), + ...conditional(searchApplicationId && { applicationId: searchApplicationId }), ...conditional(userId && { userId }), }); @@ -52,7 +61,8 @@ function AuditLogTable({ userId, className }: Props) { const isLoading = !data && !error; const { navigate } = useTenantPathname(); const [logs, totalCount] = data ?? []; - const isUserColumnVisible = !userId; + const isUserColumnVisible = + !userId && specifiedApplication?.type !== ApplicationType.MachineToMachine; const eventColumn: Column = { title: t('logs.event'), @@ -114,14 +124,16 @@ function AuditLogTable({ userId, className }: Props) { }} /> -
- { - updateSearchParameters({ applicationId, page: undefined }); - }} - /> -
+ {!applicationId && ( +
+ { + updateSearchParameters({ applicationId, page: undefined }); + }} + /> +
+ )} } placeholder={} diff --git a/packages/console/src/components/UserRolesTransfer/components/SourceRoleItem/index.module.scss b/packages/console/src/components/RolesTransfer/components/SourceRoleItem/index.module.scss similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/SourceRoleItem/index.module.scss rename to packages/console/src/components/RolesTransfer/components/SourceRoleItem/index.module.scss diff --git a/packages/console/src/components/UserRolesTransfer/components/SourceRoleItem/index.tsx b/packages/console/src/components/RolesTransfer/components/SourceRoleItem/index.tsx similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/SourceRoleItem/index.tsx rename to packages/console/src/components/RolesTransfer/components/SourceRoleItem/index.tsx diff --git a/packages/console/src/components/UserRolesTransfer/components/SourceRolesBox/index.module.scss b/packages/console/src/components/RolesTransfer/components/SourceRolesBox/index.module.scss similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/SourceRolesBox/index.module.scss rename to packages/console/src/components/RolesTransfer/components/SourceRolesBox/index.module.scss diff --git a/packages/console/src/components/UserRolesTransfer/components/SourceRolesBox/index.tsx b/packages/console/src/components/RolesTransfer/components/SourceRolesBox/index.tsx similarity index 85% rename from packages/console/src/components/UserRolesTransfer/components/SourceRolesBox/index.tsx rename to packages/console/src/components/RolesTransfer/components/SourceRolesBox/index.tsx index 9db685568..3a7dc4a16 100644 --- a/packages/console/src/components/UserRolesTransfer/components/SourceRolesBox/index.tsx +++ b/packages/console/src/components/RolesTransfer/components/SourceRolesBox/index.tsx @@ -1,4 +1,5 @@ import type { RoleResponse } from '@logto/schemas'; +import { RoleType } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import classNames from 'classnames'; import type { ChangeEvent } from 'react'; @@ -21,14 +22,15 @@ import SourceRoleItem from '../SourceRoleItem'; import * as styles from './index.module.scss'; type Props = { - userId: string; + entityId: string; + type: RoleType; selectedRoles: RoleResponse[]; onChange: (value: RoleResponse[]) => void; }; const pageSize = defaultPageSize; -function SourceRolesBox({ userId, selectedRoles, onChange }: Props) { +function SourceRolesBox({ entityId, type, selectedRoles, onChange }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const [page, setPage] = useState(1); @@ -37,9 +39,11 @@ function SourceRolesBox({ userId, selectedRoles, onChange }: Props) { const debounce = useDebounce(); const url = buildUrl('api/roles', { - excludeUserId: userId, page: String(page), page_size: String(pageSize), + 'search.type': type, + 'mode.type': 'exact', + [type === RoleType.User ? 'excludeUserId' : 'excludeApplicationId']: entityId, ...conditional(keyword && { search: `%${keyword}%` }), }); @@ -75,7 +79,14 @@ function SourceRolesBox({ userId, selectedRoles, onChange }: Props) { className={classNames(transferLayout.boxContent, isEmpty && transferLayout.emptyBoxContent)} > {isEmpty ? ( - + ) : ( dataSource.map((role) => { const isSelected = isRoleSelected(role); diff --git a/packages/console/src/components/UserRolesTransfer/components/TargetRoleItem/index.module.scss b/packages/console/src/components/RolesTransfer/components/TargetRoleItem/index.module.scss similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/TargetRoleItem/index.module.scss rename to packages/console/src/components/RolesTransfer/components/TargetRoleItem/index.module.scss diff --git a/packages/console/src/components/UserRolesTransfer/components/TargetRoleItem/index.tsx b/packages/console/src/components/RolesTransfer/components/TargetRoleItem/index.tsx similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/TargetRoleItem/index.tsx rename to packages/console/src/components/RolesTransfer/components/TargetRoleItem/index.tsx diff --git a/packages/console/src/components/UserRolesTransfer/components/TargetRolesBox/index.module.scss b/packages/console/src/components/RolesTransfer/components/TargetRolesBox/index.module.scss similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/TargetRolesBox/index.module.scss rename to packages/console/src/components/RolesTransfer/components/TargetRolesBox/index.module.scss diff --git a/packages/console/src/components/UserRolesTransfer/components/TargetRolesBox/index.tsx b/packages/console/src/components/RolesTransfer/components/TargetRolesBox/index.tsx similarity index 100% rename from packages/console/src/components/UserRolesTransfer/components/TargetRolesBox/index.tsx rename to packages/console/src/components/RolesTransfer/components/TargetRolesBox/index.tsx diff --git a/packages/console/src/components/UserRolesTransfer/index.module.scss b/packages/console/src/components/RolesTransfer/index.module.scss similarity index 100% rename from packages/console/src/components/UserRolesTransfer/index.module.scss rename to packages/console/src/components/RolesTransfer/index.module.scss diff --git a/packages/console/src/components/UserRolesTransfer/index.tsx b/packages/console/src/components/RolesTransfer/index.tsx similarity index 66% rename from packages/console/src/components/UserRolesTransfer/index.tsx rename to packages/console/src/components/RolesTransfer/index.tsx index 41b39f183..dc4fda68b 100644 --- a/packages/console/src/components/UserRolesTransfer/index.tsx +++ b/packages/console/src/components/RolesTransfer/index.tsx @@ -1,4 +1,4 @@ -import type { RoleResponse } from '@logto/schemas'; +import type { RoleResponse, RoleType } from '@logto/schemas'; import classNames from 'classnames'; import * as transferLayout from '@/scss/transfer.module.scss'; @@ -8,19 +8,20 @@ import TargetRolesBox from './components/TargetRolesBox'; import * as styles from './index.module.scss'; type Props = { - userId: string; + entityId: string; + type: RoleType; value: RoleResponse[]; onChange: (value: RoleResponse[]) => void; }; -function UserRolesTransfer({ userId, value, onChange }: Props) { +function RolesTransfer({ entityId, type, value, onChange }: Props) { return (
- +
); } -export default UserRolesTransfer; +export default RolesTransfer; diff --git a/packages/console/src/consts/page-tabs.ts b/packages/console/src/consts/page-tabs.ts index cd87e64b1..dcb0b2157 100644 --- a/packages/console/src/consts/page-tabs.ts +++ b/packages/console/src/consts/page-tabs.ts @@ -1,6 +1,8 @@ export enum ApplicationDetailsTabs { Settings = 'settings', AdvancedSettings = 'advanced-settings', + Roles = 'roles', + Logs = 'logs', } export enum ApiResourceDetailsTabs { diff --git a/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.module.scss b/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.module.scss new file mode 100644 index 000000000..8994f64b6 --- /dev/null +++ b/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.module.scss @@ -0,0 +1,7 @@ +@use '@/scss/underscore' as _; + +.logs { + flex: 1; + margin-bottom: _.unit(6); + overflow-y: auto; +} diff --git a/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.tsx b/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.tsx new file mode 100644 index 000000000..c1d0a3e1f --- /dev/null +++ b/packages/console/src/pages/ApplicationDetails/components/MachineLogs/index.tsx @@ -0,0 +1,11 @@ +import AuditLogTable from '@/components/AuditLogTable'; + +import * as styles from './index.module.scss'; + +type Props = { applicationId: string }; + +function MachineLogs({ applicationId }: Props) { + return ; +} + +export default MachineLogs; diff --git a/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.module.scss b/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.module.scss new file mode 100644 index 000000000..bea6d6132 --- /dev/null +++ b/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.module.scss @@ -0,0 +1,26 @@ +@use '@/scss/underscore' as _; + +.rolesTable { + flex: 1; + margin-bottom: _.unit(6); + color: var(--color-text); + + .filter { + display: flex; + justify-content: space-between; + align-items: center; + + .searchInput { + width: 306px; + } + } + + .name { + display: block; + @include _.text-ellipsis; + } + + .description { + @include _.text-ellipsis; + } +} diff --git a/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.tsx b/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.tsx new file mode 100644 index 000000000..02f6dfa98 --- /dev/null +++ b/packages/console/src/pages/ApplicationDetails/components/MachineToMachineApplicationRoles/index.tsx @@ -0,0 +1,183 @@ +import type { Application, Role } from '@logto/schemas'; +import { RoleType } from '@logto/schemas'; +import { conditional } from '@silverhand/essentials'; +import { useState } from 'react'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; +import useSWR from 'swr'; + +import Delete from '@/assets/icons/delete.svg'; +import Plus from '@/assets/icons/plus.svg'; +import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder'; +import { defaultPageSize } from '@/consts'; +import Button from '@/ds-components/Button'; +import ConfirmModal from '@/ds-components/ConfirmModal'; +import IconButton from '@/ds-components/IconButton'; +import Search from '@/ds-components/Search'; +import Table from '@/ds-components/Table'; +import TextLink from '@/ds-components/TextLink'; +import { Tooltip } from '@/ds-components/Tip'; +import type { RequestError } from '@/hooks/use-api'; +import useApi from '@/hooks/use-api'; +import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher'; +import AssignToRoleModal from '@/pages/Roles/components/AssignToRoleModal'; +import { buildUrl, formatSearchKeyword } from '@/utils/url'; + +import * as styles from './index.module.scss'; + +const pageSize = defaultPageSize; + +type Props = { + application: Application; +}; + +function MachineToMachineApplicationRoles({ application }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const [{ page, keyword }, updateSearchParameters] = useSearchParametersWatcher({ + page: 1, + keyword: '', + }); + + const { data, error, mutate } = useSWR<[Role[], number], RequestError>( + buildUrl(`api/applications/${application.id}/roles`, { + page: String(page), + page_size: String(pageSize), + ...conditional(keyword && { search: formatSearchKeyword(keyword) }), + }) + ); + + const isLoading = !data && !error; + + const [roles, totalCount] = data ?? []; + + const [isAssignRolesModalOpen, setIsAssignRolesModalOpen] = useState(false); + const [roleToBeDeleted, setRoleToBeDeleted] = useState(); + const [isDeleting, setIsDeleting] = useState(false); + + const api = useApi(); + + const handleDelete = async () => { + if (!roleToBeDeleted || isDeleting) { + return; + } + setIsDeleting(true); + + try { + await api.delete(`api/applications/${application.id}/roles/${roleToBeDeleted.id}`); + toast.success(t('application_details.roles.deleted', { name: roleToBeDeleted.name })); + await mutate(); + setRoleToBeDeleted(undefined); + } finally { + setIsDeleting(false); + } + }; + + return ( + <> + ( + + {name} + + ), + }, + { + title: t('application_details.roles.description_column'), + dataIndex: 'description', + colSpan: 9, + render: ({ description }) =>
{description}
, + }, + { + title: null, + dataIndex: 'delete', + colSpan: 1, + render: (role) => ( + + { + setRoleToBeDeleted(role); + }} + > + + + + ), + }, + ]} + filter={ +
+ { + updateSearchParameters({ keyword, page: 1 }); + }} + onClearSearch={() => { + updateSearchParameters({ keyword: '', page: 1 }); + }} + /> +
+ } + pagination={{ + page, + pageSize, + totalCount, + onChange: (page) => { + updateSearchParameters({ page }); + }, + }} + placeholder={} + errorMessage={error?.body?.message ?? error?.message} + onRetry={async () => mutate(undefined, true)} + /> + {roleToBeDeleted && ( + { + setRoleToBeDeleted(undefined); + }} + onConfirm={handleDelete} + > + {t('application_details.roles.delete_description')} + + )} + {isAssignRolesModalOpen && ( + { + if (success) { + void mutate(); + } + setIsAssignRolesModalOpen(false); + }} + /> + )} + + ); +} + +export default MachineToMachineApplicationRoles; diff --git a/packages/console/src/pages/ApplicationDetails/index.tsx b/packages/console/src/pages/ApplicationDetails/index.tsx index f1e09b433..c7c1eaf84 100644 --- a/packages/console/src/pages/ApplicationDetails/index.tsx +++ b/packages/console/src/pages/ApplicationDetails/index.tsx @@ -4,6 +4,7 @@ import { type ApplicationResponse, type SnakeCaseOidcConfig, customClientMetadataDefault, + ApplicationType, } from '@logto/schemas'; import { useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; @@ -21,6 +22,7 @@ import Drawer from '@/components/Drawer'; import PageMeta from '@/components/PageMeta'; import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal'; import { ApplicationDetailsTabs } from '@/consts'; +import { isDevFeaturesEnabled } from '@/consts/env'; import { openIdProviderConfigPath } from '@/consts/oidc'; import ActionMenu, { ActionMenuItem } from '@/ds-components/ActionMenu'; import Button from '@/ds-components/Button'; @@ -39,6 +41,8 @@ import { trySubmitSafe } from '@/utils/form'; import AdvancedSettings from './components/AdvancedSettings'; import GuideDrawer from './components/GuideDrawer'; import GuideModal from './components/GuideModal'; +import MachineLogs from './components/MachineLogs'; +import MachineToMachineApplicationRoles from './components/MachineToMachineApplicationRoles'; import Settings from './components/Settings'; import * as styles from './index.module.scss'; @@ -234,6 +238,18 @@ function ApplicationDetails() { > {t('application_details.advanced_settings')} + {isDevFeaturesEnabled && ( + <> + {data.type === ApplicationType.MachineToMachine && ( + + {t('application_details.application_roles')} + + )} + + {t('application_details.machine_logs')} + + + )} + {isDevFeaturesEnabled && ( + <> + {data.type === ApplicationType.MachineToMachine && ( + + + + )} + + + + + )} diff --git a/packages/console/src/pages/UserDetails/UserRoles/components/AssignRolesModal/index.tsx b/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx similarity index 51% rename from packages/console/src/pages/UserDetails/UserRoles/components/AssignRolesModal/index.tsx rename to packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx index 85d2d246f..e7b93fb97 100644 --- a/packages/console/src/pages/UserDetails/UserRoles/components/AssignRolesModal/index.tsx +++ b/packages/console/src/pages/Roles/components/AssignToRoleModal/index.tsx @@ -1,10 +1,11 @@ -import type { RoleResponse, User } from '@logto/schemas'; +import type { RoleResponse, User, Application } from '@logto/schemas'; +import { RoleType } from '@logto/schemas'; import { useState } from 'react'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import ReactModal from 'react-modal'; -import UserRolesTransfer from '@/components/UserRolesTransfer'; +import RolesTransfer from '@/components/RolesTransfer'; import Button from '@/ds-components/Button'; import DangerousRaw from '@/ds-components/DangerousRaw'; import ModalLayout from '@/ds-components/ModalLayout'; @@ -12,16 +13,21 @@ import useApi from '@/hooks/use-api'; import * as modalStyles from '@/scss/modal.module.scss'; import { getUserTitle } from '@/utils/user'; -type Props = { - user: User; - onClose: (success?: boolean) => void; -}; +type Props = + | { + entity: User; + onClose: (success?: boolean) => void; + type: RoleType.User; + } + | { + entity: Application; + onClose: (success?: boolean) => void; + type: RoleType.MachineToMachine; + }; -function AssignRolesModal({ user, onClose }: Props) { +function AssignToRoleModal({ entity, onClose, type }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const userName = getUserTitle(user); - const [isSubmitting, setIsSubmitting] = useState(false); const [roles, setRoles] = useState([]); @@ -35,9 +41,12 @@ function AssignRolesModal({ user, onClose }: Props) { setIsSubmitting(true); try { - await api.post(`api/users/${user.id}/roles`, { - json: { roleIds: roles.map(({ id }) => id) }, - }); + await api.post( + `api/${type === RoleType.User ? 'users' : 'applications'}/${entity.id}/roles`, + { + json: { roleIds: roles.map(({ id }) => id) }, + } + ); toast.success(t('user_details.roles.role_assigned')); onClose(true); } finally { @@ -57,10 +66,24 @@ function AssignRolesModal({ user, onClose }: Props) { > {t('user_details.roles.assign_title', { name: userName })} + + {t( + type === RoleType.User + ? 'user_details.roles.assign_title' + : 'application_details.roles.assign_title', + { name: type === RoleType.User ? getUserTitle(entity) : entity.name } + )} + } subtitle={ - {t('user_details.roles.assign_subtitle', { name: userName })} + + {t( + type === RoleType.User + ? 'user_details.roles.assign_subtitle' + : 'application_details.roles.assign_subtitle', + { name: type === RoleType.User ? getUserTitle(entity) : entity.name } + )} + } size="large" footer={ @@ -68,7 +91,11 @@ function AssignRolesModal({ user, onClose }: Props) { isLoading={isSubmitting} disabled={roles.length === 0} htmlType="submit" - title="user_details.roles.confirm_assign" + title={ + type === RoleType.User + ? 'user_details.roles.confirm_assign' + : 'application_details.roles.confirm_assign' + } size="large" type="primary" onClick={handleAssign} @@ -76,8 +103,9 @@ function AssignRolesModal({ user, onClose }: Props) { } onClose={onClose} > - { setRoles(value); @@ -88,4 +116,4 @@ function AssignRolesModal({ user, onClose }: Props) { ); } -export default AssignRolesModal; +export default AssignToRoleModal; diff --git a/packages/console/src/pages/UserDetails/UserRoles/index.tsx b/packages/console/src/pages/UserDetails/UserRoles/index.tsx index f0237ea7e..98d52995e 100644 --- a/packages/console/src/pages/UserDetails/UserRoles/index.tsx +++ b/packages/console/src/pages/UserDetails/UserRoles/index.tsx @@ -1,4 +1,5 @@ import type { Role } from '@logto/schemas'; +import { RoleType } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import { useState } from 'react'; import { toast } from 'react-hot-toast'; @@ -20,11 +21,11 @@ import { Tooltip } from '@/ds-components/Tip'; import type { RequestError } from '@/hooks/use-api'; import useApi from '@/hooks/use-api'; import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher'; +import AssignToRoleModal from '@/pages/Roles/components/AssignToRoleModal'; import { buildUrl, formatSearchKeyword } from '@/utils/url'; import type { UserDetailsOutletContext } from '../types'; -import AssignRolesModal from './components/AssignRolesModal'; import * as styles from './index.module.scss'; const pageSize = defaultPageSize; @@ -166,8 +167,9 @@ function UserRoles() { )} {isAssignRolesModalOpen && ( - { if (success) { void mutate(); diff --git a/packages/core/src/queries/roles.ts b/packages/core/src/queries/roles.ts index 894eec210..566ba6e36 100644 --- a/packages/core/src/queries/roles.ts +++ b/packages/core/src/queries/roles.ts @@ -4,6 +4,7 @@ import type { OmitAutoSetFields } from '@logto/shared'; import { conditionalArraySql, conditionalSql, convertToIdentifiers } from '@logto/shared'; import type { CommonQueryMethods } from 'slonik'; import { sql } from 'slonik'; +import { snakeCase } from 'snake-case'; import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; @@ -16,11 +17,19 @@ const { table, fields } = convertToIdentifiers(Roles); const buildRoleConditions = (search: Search) => { const hasSearch = search.matches.length > 0; - const searchFields = [Roles.fields.id, Roles.fields.name, Roles.fields.description]; + const searchFields = [ + Roles.fields.id, + Roles.fields.name, + Roles.fields.description, + Roles.fields.type, + ]; return conditionalSql( hasSearch, - () => sql`and ${buildConditionsFromSearch(search, searchFields)}` + () => + sql`and ${buildConditionsFromSearch(search, searchFields, { + [Roles.fields.type]: snakeCase('RoleType'), + })}` ); }; diff --git a/packages/core/src/routes/role.ts b/packages/core/src/routes/role.ts index c9764ef59..d98482aa7 100644 --- a/packages/core/src/routes/role.ts +++ b/packages/core/src/routes/role.ts @@ -36,7 +36,11 @@ export default function roleRoutes(...[router, tenant]: users: { findUsersByIds }, usersRoles: { countUsersRolesByRoleId, findUsersRolesByRoleId, findUsersRolesByUserId }, applications: { findApplicationsByIds }, - applicationsRoles: { countApplicationsRolesByRoleId, findApplicationsRolesByRoleId }, + applicationsRoles: { + countApplicationsRolesByRoleId, + findApplicationsRolesByRoleId, + findApplicationsRolesByApplicationId, + }, } = queries; router.use('/roles(/.*)?', koaRoleRlsErrorHandler()); @@ -75,9 +79,19 @@ export default function roleRoutes(...[router, tenant]: return tryThat( async () => { const search = parseSearchParamsForSearch(searchParams); + const excludeUserId = searchParams.get('excludeUserId'); const usersRoles = excludeUserId ? await findUsersRolesByUserId(excludeUserId) : []; - const excludeRoleIds = usersRoles.map(({ roleId }) => roleId); + + const excludeApplicationId = searchParams.get('excludeApplicationId'); + const applicationsRoles = excludeApplicationId + ? await findApplicationsRolesByApplicationId(excludeApplicationId) + : []; + + const excludeRoleIds = [ + ...usersRoles.map(({ roleId }) => roleId), + ...applicationsRoles.map(({ roleId }) => roleId), + ]; const [{ count }, roles] = await Promise.all([ countRoles(search, { excludeRoleIds }), diff --git a/packages/integration-tests/src/api/role.ts b/packages/integration-tests/src/api/role.ts index 72d754b92..1cd502f83 100644 --- a/packages/integration-tests/src/api/role.ts +++ b/packages/integration-tests/src/api/role.ts @@ -5,6 +5,8 @@ import { generateRoleName } from '#src/utils.js'; import { authedAdminApi } from './api.js'; +export type GetRoleOptions = { excludeUserId?: string; excludeApplicationId?: string }; + export const createRole = async ({ name, description, @@ -27,7 +29,8 @@ export const createRole = async ({ }) .json(); -export const getRoles = async () => authedAdminApi.get('roles').json(); +export const getRoles = async (options?: GetRoleOptions) => + authedAdminApi.get('roles', { searchParams: new URLSearchParams(options) }).json(); export const getRole = async (roleId: string) => authedAdminApi.get(`roles/${roleId}`).json(); diff --git a/packages/integration-tests/src/tests/api/role.application.test.ts b/packages/integration-tests/src/tests/api/role.application.test.ts index 2d99a4dc7..d05a06fa6 100644 --- a/packages/integration-tests/src/tests/api/role.application.test.ts +++ b/packages/integration-tests/src/tests/api/role.application.test.ts @@ -7,11 +7,12 @@ import { assignApplicationsToRole, createRole, deleteApplicationFromRole, + getRoles, getRoleApplications, } from '#src/api/role.js'; describe('roles applications', () => { - it('should get role applications successfully', async () => { + it('should get role applications successfully and get roles correctly (specifying exclude application)', async () => { const role = await createRole({ type: RoleType.MachineToMachine }); const m2mApp = await createApplication(generateStandardId(), ApplicationType.MachineToMachine); await assignApplicationsToRole([m2mApp.id], role.id); @@ -19,6 +20,9 @@ describe('roles applications', () => { expect(applications.length).toBe(1); expect(applications[0]).toHaveProperty('id', m2mApp.id); + + const allRolesWithoutAppsRoles = await getRoles({ excludeApplicationId: m2mApp.id }); + expect(allRolesWithoutAppsRoles.find(({ id }) => id === role.id)).toBeUndefined(); }); it('should return 404 if role not found', async () => { diff --git a/packages/integration-tests/src/tests/api/role.user.test.ts b/packages/integration-tests/src/tests/api/role.user.test.ts index 3addd6936..d59b50f86 100644 --- a/packages/integration-tests/src/tests/api/role.user.test.ts +++ b/packages/integration-tests/src/tests/api/role.user.test.ts @@ -2,12 +2,18 @@ import { RoleType } from '@logto/schemas'; import { HTTPError } from 'got'; import { createUser } from '#src/api/index.js'; -import { assignUsersToRole, createRole, deleteUserFromRole, getRoleUsers } from '#src/api/role.js'; +import { + assignUsersToRole, + createRole, + deleteUserFromRole, + getRoles, + getRoleUsers, +} from '#src/api/role.js'; import { expectRejects } from '#src/helpers/index.js'; import { generateNewUserProfile } from '#src/helpers/user.js'; describe('roles users', () => { - it('should get role users successfully', async () => { + it('should get role users successfully and can get roles correctly (specifying exclude user)', async () => { const role = await createRole({}); const user = await createUser(generateNewUserProfile({})); await assignUsersToRole([user.id], role.id); @@ -15,6 +21,9 @@ describe('roles users', () => { expect(users.length).toBe(1); expect(users[0]).toHaveProperty('id', user.id); + + const allRolesWithoutUsersRoles = await getRoles({ excludeUserId: user.id }); + expect(allRolesWithoutUsersRoles.find(({ id }) => id === role.id)).toBeUndefined(); }); it('should return 404 if role not found', async () => { diff --git a/packages/phrases/src/locales/de/translation/admin-console/application-details.ts b/packages/phrases/src/locales/de/translation/admin-console/application-details.ts index c57081ec2..b25d7d18e 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Erweiterte Einstellungen', advanced_settings_description: 'Erweiterte Einstellungen beinhalten OIDC-bezogene Begriffe. Sie können den Token-Endpunkt für weitere Informationen überprüfen.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Anwendungsname', application_name_placeholder: 'Meine App', description: 'Beschreibung', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Gib einen Anwendungsnamen ein', application_deleted: 'Anwendung {{name}} wurde erfolgreich gelöscht', redirect_uri_required: 'Gib mindestens eine Umleitungs-URI an', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts index e996d3226..bbff4a04d 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Advanced Settings', advanced_settings_description: 'Advanced settings include OIDC related terms. You can check out the Token Endpoint for more information.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Application name', application_name_placeholder: 'My App', description: 'Description', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Enter your application name', application_deleted: 'Application {{name}} has been successfully deleted', redirect_uri_required: 'You must enter at least one redirect URI', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/es/translation/admin-console/application-details.ts b/packages/phrases/src/locales/es/translation/admin-console/application-details.ts index 6c84101f9..aee7d2b2f 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Configuraciones Avanzadas', advanced_settings_description: 'Las configuraciones avanzadas incluyen términos relacionados con OIDC. Puedes revisar el Endpoint del Token para obtener más información.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Nombre de Aplicación', application_name_placeholder: 'Mi App', description: 'Descripción', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Ingresa el nombre de tu aplicación', application_deleted: 'Se ha eliminado exitosamente la aplicación {{name}}', redirect_uri_required: 'Debes ingresar al menos un URI de Redireccionamiento', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/fr/translation/admin-console/application-details.ts b/packages/phrases/src/locales/fr/translation/admin-console/application-details.ts index 852288637..5d90c4b52 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Paramètres avancés', advanced_settings_description: "Les paramètres avancés comprennent des termes liés à OIDC. Vous pouvez consulter le point de terminaison de jeton pour plus d'informations.", + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: "Nom de l'application", application_name_placeholder: 'Mon App', description: 'Description', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Entrez le nom de votre application', application_deleted: "L'application {{name}} a été supprimée avec succès.", redirect_uri_required: 'Vous devez entrer au moins un URI de redirection.', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/it/translation/admin-console/application-details.ts b/packages/phrases/src/locales/it/translation/admin-console/application-details.ts index ecdfd94bb..5a22710bd 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Impostazioni avanzate', advanced_settings_description: "Le impostazioni avanzate includono termini correlati all'OIDC. Puoi consultare il Endpoint Token per ulteriori informazioni.", + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: "Nome dell'applicazione", application_name_placeholder: 'La mia app', description: 'Descrizione', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Inserisci il nome della tua applicazione', application_deleted: "L'applicazione {{name}} è stata eliminata con successo", redirect_uri_required: 'Devi inserire almeno un URI di reindirizzamento', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/ja/translation/admin-console/application-details.ts b/packages/phrases/src/locales/ja/translation/admin-console/application-details.ts index e2167eafd..b08238dd6 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: '高度な設定', advanced_settings_description: '高度な設定にはOIDC関連用語が含まれます。詳細については、トークンエンドポイントを確認してください。', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'アプリケーション名', application_name_placeholder: '私のアプリ', description: '説明', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'アプリケーション名を入力してください', application_deleted: 'アプリケーション{{name}}が正常に削除されました', redirect_uri_required: 'リダイレクトURIを少なくとも1つ入力する必要があります', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/ko/translation/admin-console/application-details.ts b/packages/phrases/src/locales/ko/translation/admin-console/application-details.ts index 4190814d0..8491851e2 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: '고급 설정', advanced_settings_description: '고급 설정에는 OIDC 관련 용어가 포함돼요. 자세한 내용은 토큰 엔드포인트에서 확인할 수 있어요.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: '어플리케이션 이름', application_name_placeholder: '나의 앱', description: '설명', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: '어플리케이션 이름을 입력해 주세요.', application_deleted: '{{name}} 어플리케이션이 성공적으로 삭제되었어요.', redirect_uri_required: '반드시 최소 하나의 Redirect URI 를 입력해야 해요.', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/application-details.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/application-details.ts index 0caf09143..584ce1f50 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Zaawansowane ustawienia', advanced_settings_description: 'Zaawansowane ustawienia obejmują związane z OIDC terminy. Możesz sprawdzić punkt końcowy Token dla bardziej szczegółowych informacji.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Nazwa aplikacji', application_name_placeholder: 'Moja aplikacja', description: 'Opis', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Wpisz nazwę swojej aplikacji', application_deleted: 'Aplikacja {{name}} została pomyślnie usunięta', redirect_uri_required: 'Musisz wpisać co najmniej jeden adres URL przekierowania', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/application-details.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/application-details.ts index 80352f83c..beccf4176 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Configurações avançadas', advanced_settings_description: 'As configurações avançadas incluem termos relacionados ao OIDC. Você pode conferir o Token Endpoint para obter mais informações.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Nome do aplicativo', application_name_placeholder: 'Meu aplicativo', description: 'Descrição', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Digite o nome do seu aplicativo', application_deleted: 'O aplicativo {{name}} foi excluído com sucesso', redirect_uri_required: 'Você deve inserir pelo menos um URI de redirecionamento', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/application-details.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/application-details.ts index 10066c1b6..b7c027583 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Configurações avançadas', advanced_settings_description: 'As configurações avançadas incluem termos relacionados com OIDC. Pode consultar o Endpoint do Token para obter mais informações.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Nome da aplicação', application_name_placeholder: 'Ex: Site da Empresa', description: 'Descrição', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Insira o nome da aplicação', application_deleted: 'Aplicação {{name}} eliminada com sucesso', redirect_uri_required: 'Deve inserir pelo menos um URI de redirecionamento', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/ru/translation/admin-console/application-details.ts b/packages/phrases/src/locales/ru/translation/admin-console/application-details.ts index ab4e06542..2cf702fe0 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Расширенные настройки', advanced_settings_description: 'Расширенные настройки включают связанные с OIDC термины. Вы можете проверить конечную точку токена для получения дополнительной информации.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Название приложения', application_name_placeholder: 'Мое приложение', description: 'Описание', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Введите название своего приложения', application_deleted: 'Приложение {{name}} успешно удалено', redirect_uri_required: 'Вы должны ввести по крайней мере один URI перенаправления', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/application-details.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/application-details.ts index 34780f507..3a1d9375a 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/application-details.ts @@ -8,6 +8,10 @@ const application_details = { advanced_settings: 'Gelişmiş Ayarlar', advanced_settings_description: 'Gelişmiş ayarlar, OIDC ile ilgili terimleri içerir. Daha fazla bilgi için Token Bitiş Noktasına bakabilirsiniz.', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: 'Uygulama Adı', application_name_placeholder: 'Uygulamam', description: 'Açıklama', @@ -55,6 +59,39 @@ const application_details = { enter_your_application_name: 'Uygulama adı giriniz', application_deleted: '{{name}} Uygulaması başarıyla silindi', redirect_uri_required: 'En az 1 yönlendirme URIı girmelisiniz', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/application-details.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/application-details.ts index 0428747b3..aba8f1930 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/application-details.ts @@ -7,6 +7,10 @@ const application_details = { advanced_settings: '高级设置', advanced_settings_description: '高级设置包括 OIDC 相关术语。你可以查看 Token Endpoint 以获取更多信息。', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: '应用名称', application_name_placeholder: '我的应用', description: '描述', @@ -52,6 +56,39 @@ const application_details = { enter_your_application_name: '输入你的应用名称', application_deleted: '应用 {{name}} 成功删除。', redirect_uri_required: '至少需要输入一个重定向 URI。', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/application-details.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/application-details.ts index a580c8918..531b7ca47 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/application-details.ts @@ -7,6 +7,10 @@ const application_details = { advanced_settings: '高級設定', advanced_settings_description: '高級設定包括 OIDC 相關術語。你可以查看 Token Endpoint 以獲取更多資訊。', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: '應用程式名稱', application_name_placeholder: '我的應用程式', description: '描述', @@ -52,6 +56,39 @@ const application_details = { enter_your_application_name: '輸入你的應用程式名稱', application_deleted: '應用 {{name}} 成功刪除。', redirect_uri_required: '至少需要輸入一個重定向 URL。', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details); diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/application-details.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/application-details.ts index f97403c83..fcb871b8c 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/application-details.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/application-details.ts @@ -7,6 +7,10 @@ const application_details = { advanced_settings: '高級設置', advanced_settings_description: '高級設置包括 OIDC 相關術語。你可以查看 Token Endpoint 以獲取更多信息。', + /** UNTRANSLATED */ + application_roles: 'Roles', + /** UNTRANSLATED */ + machine_logs: 'Machine logs', application_name: '應用程式姓名', application_name_placeholder: '我的應用程式', description: '說明', @@ -53,6 +57,39 @@ const application_details = { enter_your_application_name: '輸入你的應用程式姓名', application_deleted: '應用 {{name}} 成功刪除。', redirect_uri_required: '至少需要輸入一個重定向 URL。', + roles: { + /** UNTRANSLATED */ + name_column: 'Role', + /** UNTRANSLATED */ + description_column: 'Description', + /** UNTRANSLATED */ + assign_button: 'Assign Roles', + /** UNTRANSLATED */ + delete_description: + 'This action will remove this role from this user. The role itself will still exist, but it will no longer be associated with this user.', + /** UNTRANSLATED */ + deleted: '{{name}} was successfully removed from this user.', + /** UNTRANSLATED */ + assign_title: 'Assign roles to {{name}}', + /** UNTRANSLATED */ + assign_subtitle: 'Authorize {{name}} one or more roles', + /** UNTRANSLATED */ + assign_role_field: 'Assign roles', + /** UNTRANSLATED */ + role_search_placeholder: 'Search by role name', + /** UNTRANSLATED */ + added_text: '{{value, number}} added', + /** UNTRANSLATED */ + assigned_user_count: '{{value, number}} users', + /** UNTRANSLATED */ + confirm_assign: 'Assign roles', + /** UNTRANSLATED */ + role_assigned: 'Successfully assigned role(s)', + /** UNTRANSLATED */ + search: 'Search by role name, description or ID', + /** UNTRANSLATED */ + empty: 'No role available', + }, }; export default Object.freeze(application_details);