mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(console,phrases,core): add roles and logs tabs for m2m app details page (#4548)
This commit is contained in:
parent
fc252b56f0
commit
352ca03177
39 changed files with 961 additions and 56 deletions
|
@ -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<ApplicationResponse>(
|
||||
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<Log> = {
|
||||
title: t('logs.event'),
|
||||
|
@ -114,14 +124,16 @@ function AuditLogTable({ userId, className }: Props) {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.applicationSelector}>
|
||||
<ApplicationSelector
|
||||
value={applicationId}
|
||||
onChange={(applicationId) => {
|
||||
updateSearchParameters({ applicationId, page: undefined });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!applicationId && (
|
||||
<div className={styles.applicationSelector}>
|
||||
<ApplicationSelector
|
||||
value={applicationId}
|
||||
onChange={(applicationId) => {
|
||||
updateSearchParameters({ applicationId, page: undefined });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
|
|
|
@ -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 ? (
|
||||
<EmptyDataPlaceholder size="small" title={t('user_details.roles.empty')} />
|
||||
<EmptyDataPlaceholder
|
||||
size="small"
|
||||
title={t(
|
||||
type === RoleType.User
|
||||
? 'user_details.roles.empty'
|
||||
: 'application_details.roles.empty'
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
dataSource.map((role) => {
|
||||
const isSelected = isRoleSelected(role);
|
|
@ -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 (
|
||||
<div className={classNames(transferLayout.container, styles.rolesTransfer)}>
|
||||
<SourceRolesBox userId={userId} selectedRoles={value} onChange={onChange} />
|
||||
<SourceRolesBox entityId={entityId} type={type} selectedRoles={value} onChange={onChange} />
|
||||
<div className={transferLayout.verticalBar} />
|
||||
<TargetRolesBox selectedRoles={value} onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserRolesTransfer;
|
||||
export default RolesTransfer;
|
|
@ -1,6 +1,8 @@
|
|||
export enum ApplicationDetailsTabs {
|
||||
Settings = 'settings',
|
||||
AdvancedSettings = 'advanced-settings',
|
||||
Roles = 'roles',
|
||||
Logs = 'logs',
|
||||
}
|
||||
|
||||
export enum ApiResourceDetailsTabs {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.logs {
|
||||
flex: 1;
|
||||
margin-bottom: _.unit(6);
|
||||
overflow-y: auto;
|
||||
}
|
|
@ -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 <AuditLogTable applicationId={applicationId} className={styles.logs} />;
|
||||
}
|
||||
|
||||
export default MachineLogs;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Role>();
|
||||
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 (
|
||||
<>
|
||||
<Table
|
||||
className={styles.rolesTable}
|
||||
isLoading={isLoading}
|
||||
rowGroups={[{ key: 'roles', data: roles }]}
|
||||
rowIndexKey="id"
|
||||
columns={[
|
||||
{
|
||||
title: t('application_details.roles.name_column'),
|
||||
dataIndex: 'name',
|
||||
colSpan: 6,
|
||||
render: ({ id, name }) => (
|
||||
<TextLink className={styles.name} to={`/roles/${id}`}>
|
||||
{name}
|
||||
</TextLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('application_details.roles.description_column'),
|
||||
dataIndex: 'description',
|
||||
colSpan: 9,
|
||||
render: ({ description }) => <div className={styles.description}>{description}</div>,
|
||||
},
|
||||
{
|
||||
title: null,
|
||||
dataIndex: 'delete',
|
||||
colSpan: 1,
|
||||
render: (role) => (
|
||||
<Tooltip content={t('general.remove')}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setRoleToBeDeleted(role);
|
||||
}}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
]}
|
||||
filter={
|
||||
<div className={styles.filter}>
|
||||
<Search
|
||||
inputClassName={styles.searchInput}
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
placeholder={t('application_details.roles.search')}
|
||||
onSearch={(keyword) => {
|
||||
updateSearchParameters({ keyword, page: 1 });
|
||||
}}
|
||||
onClearSearch={() => {
|
||||
updateSearchParameters({ keyword: '', page: 1 });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="application_details.roles.assign_button"
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<Plus />}
|
||||
onClick={() => {
|
||||
setIsAssignRolesModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
pagination={{
|
||||
page,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onChange: (page) => {
|
||||
updateSearchParameters({ page });
|
||||
},
|
||||
}}
|
||||
placeholder={<EmptyDataPlaceholder />}
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
onRetry={async () => mutate(undefined, true)}
|
||||
/>
|
||||
{roleToBeDeleted && (
|
||||
<ConfirmModal
|
||||
isOpen
|
||||
isLoading={isDeleting}
|
||||
confirmButtonText="general.remove"
|
||||
onCancel={() => {
|
||||
setRoleToBeDeleted(undefined);
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
{t('application_details.roles.delete_description')}
|
||||
</ConfirmModal>
|
||||
)}
|
||||
{isAssignRolesModalOpen && (
|
||||
<AssignToRoleModal
|
||||
entity={application}
|
||||
type={RoleType.MachineToMachine}
|
||||
onClose={(success) => {
|
||||
if (success) {
|
||||
void mutate();
|
||||
}
|
||||
setIsAssignRolesModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MachineToMachineApplicationRoles;
|
|
@ -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')}
|
||||
</TabNavItem>
|
||||
{isDevFeaturesEnabled && (
|
||||
<>
|
||||
{data.type === ApplicationType.MachineToMachine && (
|
||||
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Roles}`}>
|
||||
{t('application_details.application_roles')}
|
||||
</TabNavItem>
|
||||
)}
|
||||
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Logs}`}>
|
||||
{t('application_details.machine_logs')}
|
||||
</TabNavItem>
|
||||
</>
|
||||
)}
|
||||
</TabNav>
|
||||
<FormProvider {...formMethods}>
|
||||
<DetailsForm
|
||||
|
@ -248,6 +264,18 @@ function ApplicationDetails() {
|
|||
<TabWrapper isActive={tab === ApplicationDetailsTabs.AdvancedSettings}>
|
||||
<AdvancedSettings app={data} oidcConfig={oidcConfig} />
|
||||
</TabWrapper>
|
||||
{isDevFeaturesEnabled && (
|
||||
<>
|
||||
{data.type === ApplicationType.MachineToMachine && (
|
||||
<TabWrapper isActive={tab === ApplicationDetailsTabs.Roles}>
|
||||
<MachineToMachineApplicationRoles application={data} />
|
||||
</TabWrapper>
|
||||
)}
|
||||
<TabWrapper isActive={tab === ApplicationDetailsTabs.Logs}>
|
||||
<MachineLogs applicationId={data.id} />
|
||||
</TabWrapper>
|
||||
</>
|
||||
)}
|
||||
</DetailsForm>
|
||||
</FormProvider>
|
||||
</>
|
||||
|
|
|
@ -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<RoleResponse[]>([]);
|
||||
|
||||
|
@ -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) {
|
|||
>
|
||||
<ModalLayout
|
||||
title={
|
||||
<DangerousRaw>{t('user_details.roles.assign_title', { name: userName })}</DangerousRaw>
|
||||
<DangerousRaw>
|
||||
{t(
|
||||
type === RoleType.User
|
||||
? 'user_details.roles.assign_title'
|
||||
: 'application_details.roles.assign_title',
|
||||
{ name: type === RoleType.User ? getUserTitle(entity) : entity.name }
|
||||
)}
|
||||
</DangerousRaw>
|
||||
}
|
||||
subtitle={
|
||||
<DangerousRaw>{t('user_details.roles.assign_subtitle', { name: userName })}</DangerousRaw>
|
||||
<DangerousRaw>
|
||||
{t(
|
||||
type === RoleType.User
|
||||
? 'user_details.roles.assign_subtitle'
|
||||
: 'application_details.roles.assign_subtitle',
|
||||
{ name: type === RoleType.User ? getUserTitle(entity) : entity.name }
|
||||
)}
|
||||
</DangerousRaw>
|
||||
}
|
||||
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}
|
||||
>
|
||||
<UserRolesTransfer
|
||||
userId={user.id}
|
||||
<RolesTransfer
|
||||
entityId={entity.id}
|
||||
type={type}
|
||||
value={roles}
|
||||
onChange={(value) => {
|
||||
setRoles(value);
|
||||
|
@ -88,4 +116,4 @@ function AssignRolesModal({ user, onClose }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default AssignRolesModal;
|
||||
export default AssignToRoleModal;
|
|
@ -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() {
|
|||
</ConfirmModal>
|
||||
)}
|
||||
{isAssignRolesModalOpen && (
|
||||
<AssignRolesModal
|
||||
user={user}
|
||||
<AssignToRoleModal
|
||||
entity={user}
|
||||
type={RoleType.User}
|
||||
onClose={(success) => {
|
||||
if (success) {
|
||||
void mutate();
|
||||
|
|
|
@ -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'),
|
||||
})}`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -36,7 +36,11 @@ export default function roleRoutes<T extends AuthedRouter>(...[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<T extends AuthedRouter>(...[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 }),
|
||||
|
|
|
@ -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<Role>();
|
||||
|
||||
export const getRoles = async () => authedAdminApi.get('roles').json<Role[]>();
|
||||
export const getRoles = async (options?: GetRoleOptions) =>
|
||||
authedAdminApi.get('roles', { searchParams: new URLSearchParams(options) }).json<Role[]>();
|
||||
|
||||
export const getRole = async (roleId: string) => authedAdminApi.get(`roles/${roleId}`).json<Role>();
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue