Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(console): move organization template into a single page #5590

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/console/src/assets/icons/research.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/console/src/consts/external-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ export const logtoThirdPartyAppPermissionsLink =
'/docs/recipes/logto-as-idp/permissions-management/';
export const logtoThirdPartyAppBrandingLink = '/docs/recipes/logto-as-idp/branding-customization/';
export const signingKeysLink = '/docs/recipes/openid-connect/signing-keys-rotation/';
export const organizationTemplateLink =
'/docs/recipes/organizations/understand-how-it-works/#organization-template';
export const organizationRoleLink =
'/docs/recipes/organizations/understand-how-it-works/#organization-role';
export const organizationPermissionLink =
'/docs/recipes/organizations/understand-how-it-works/#organization-permission';
5 changes: 5 additions & 0 deletions packages/console/src/consts/page-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ export enum EnterpriseSsoDetailsTabs {
Connection = 'connection',
Experience = 'experience',
}

export enum OrganizationTemplateTabs {
OrgRoles = 'org-roles',
OrgPermissions = 'org-permissions',
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ export const useSidebarMenuItems = (): {
Icon: Role,
title: 'roles',
},
{
Icon: Role,
title: 'organization_template',
isHidden: !isDevFeaturesEnabled,
},
],
},
{
Expand Down
17 changes: 17 additions & 0 deletions packages/console/src/containers/ConsoleContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TenantSettingsTabs,
ApplicationDetailsTabs,
EnterpriseSsoDetailsTabs,
OrganizationTemplateTabs,
} from '@/consts';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
Expand All @@ -33,6 +34,9 @@ import JwtClaims from '@/pages/JwtClaims';
import Mfa from '@/pages/Mfa';
import NotFound from '@/pages/NotFound';
import OrganizationDetails from '@/pages/OrganizationDetails';
import OrganizationTemplate from '@/pages/OrganizationTemplate';
import OrgPermissions from '@/pages/OrganizationTemplate/OrgPermissions';
import OrgRoles from '@/pages/OrganizationTemplate/OrgRoles';
import Organizations from '@/pages/Organizations';
import OrganizationGuide from '@/pages/Organizations/Guide';
import Profile from '@/pages/Profile';
Expand Down Expand Up @@ -180,6 +184,19 @@ function ConsoleContent() {
<Route path={RoleDetailsTabs.M2mApps} element={<RoleApplications />} />
</Route>
</Route>
{isDevFeaturesEnabled && (
<Route path="organization-template" element={<OrganizationTemplate />}>
<Route
index
element={<Navigate replace to={OrganizationTemplateTabs.OrgRoles} />}
/>
<Route path={OrganizationTemplateTabs.OrgRoles} element={<OrgRoles />} />
<Route
path={OrganizationTemplateTabs.OrgPermissions}
element={<OrgPermissions />}
/>
</Route>
)}
<Route path="organizations">
<Route index element={<Organizations />} />
<Route path="create" element={<Organizations />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@
color: var(--color-text-secondary);
}

.learnMore:not(:first-child) {
margin-left: _.unit(1);
}

&.large {
.title {
font: var(--font-title-1);
Expand Down
15 changes: 8 additions & 7 deletions packages/console/src/ds-components/CardTitle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ function CardTitle({
<span>{typeof subtitle === 'string' ? <DynamicT forKey={subtitle} /> : subtitle}</span>
)}
{learnMoreLink?.href && (
<TextLink
href={learnMoreLink.href}
targetBlank={learnMoreLink.targetBlank}
className={styles.learnMore}
>
{t('general.learn_more')}
</TextLink>
<>
{/* Use a space to keep the link and the text separate.
* Avoid using `margin-left` since it will cause an unexpected gap when the "learn more" text is at the start of a new line
*/}{' '}
<TextLink href={learnMoreLink.href} targetBlank={learnMoreLink.targetBlank}>
{t('general.learn_more')}
</TextLink>
</>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '@/scss/underscore' as _;

.filter {
display: flex;
justify-content: space-between;
align-items: center;

.createButton {
margin-left: _.unit(2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { type OrganizationScope } from '@logto/schemas';
import { cond } from '@silverhand/essentials';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';

import Plus from '@/assets/icons/plus.svg';
import PermissionsEmptyDark from '@/assets/images/permissions-empty-dark.svg';
import PermissionsEmpty from '@/assets/images/permissions-empty.svg';
import ActionsButton from '@/components/ActionsButton';
import Breakable from '@/components/Breakable';
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
import { organizationPermissionLink } from '@/consts';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import Search from '@/ds-components/Search';
import Table from '@/ds-components/Table';
import TablePlaceholder from '@/ds-components/Table/TablePlaceholder';
import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl, formatSearchKeyword } from '@/utils/url';

import * as styles from './index.module.scss';

function OrgPermissions() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getDocumentationUrl } = useDocumentationUrl();
const [{ keyword }, updateSearchParameters] = useSearchParametersWatcher({
keyword: '',
});

const { data, error, mutate, isLoading } = useSWR<OrganizationScope[], RequestError>(
buildUrl('api/organization-scopes', {
...cond(keyword && { q: formatSearchKeyword(keyword) }),
})
);

return (
<Table
rowGroups={[{ key: 'orgPermissions', data }]}
rowIndexKey="id"
columns={[
{
title: <DynamicT forKey="organization_template.org_permissions.permission_column" />,
dataIndex: 'name',
colSpan: 7,
render: ({ name }) => {
return (
<Tag variant="cell">
<Breakable>{name}</Breakable>
</Tag>
);
},
},
{
title: <DynamicT forKey="organization_template.org_permissions.description_column" />,
dataIndex: 'scopes',
colSpan: 8,
render: ({ description }) => <Breakable>{description ?? '-'}</Breakable>,
},
{
title: null,
dataIndex: 'action',
colSpan: 1,
render: () => (
<ActionsButton
fieldName="organization_template.org_permissions.permission_column"
deleteConfirmation="organization_template.org_permissions.delete_confirm"
onEdit={() => {
// Todo @xiaoyijun implement edit

Check warning on line 71 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L71

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement edit'.
}}
onDelete={async () => {
// Todo @xiaoyijun implement deletion

Check warning on line 74 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L74

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement deletion'.
}}
/>
),
},
]}
filter={
<div className={styles.filter}>
<Search
placeholder={t('organization_template.org_permissions.search_placeholder')}
defaultValue={keyword}
isClearable={Boolean(keyword)}
onSearch={(keyword) => {
updateSearchParameters({ keyword });
}}
onClearSearch={() => {
updateSearchParameters({ keyword: '' });
}}
/>
<Button
title="organization_template.org_permissions.create_org_permission"
className={styles.createButton}
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
// Todo @xiaoyijun implement org permission creation

Check warning on line 100 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L100

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement org permission...'.
}}
/>
</div>
}
placeholder={
keyword ? (
<EmptyDataPlaceholder />
) : (
<TablePlaceholder
image={<PermissionsEmpty />}
imageDark={<PermissionsEmptyDark />}
title="organization_template.org_permissions.placeholder_title"
description="organization_template.org_permissions.placeholder_description"
learnMoreLink={{
href: getDocumentationUrl(organizationPermissionLink),
targetBlank: 'noopener',
}}
action={
<Button
title="organization_template.org_permissions.create_org_permission"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
// Todo @xiaoyijun implement org permission creation

Check warning on line 125 in packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgPermissions/index.tsx#L125

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implement org permission...'.
}}
/>
}
/>
)
}
isLoading={isLoading}
errorMessage={error?.body?.message ?? error?.message}
onRetry={async () => mutate(undefined, true)}
/>
);
}

export default OrgPermissions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '@/scss/underscore' as _;

.permissions {
display: flex;
flex-wrap: wrap;
gap: _.unit(2);
}

.filter {
display: flex;
flex-direction: row-reverse;
align-items: center;
}
126 changes: 126 additions & 0 deletions packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { type OrganizationRoleWithScopes } from '@logto/schemas';
import useSWR from 'swr';

import Plus from '@/assets/icons/plus.svg';
import OrgRoleIcon from '@/assets/icons/role-feature.svg';
import RolesEmptyDark from '@/assets/images/roles-empty-dark.svg';
import RolesEmpty from '@/assets/images/roles-empty.svg';
import Breakable from '@/components/Breakable';
import ItemPreview from '@/components/ItemPreview';
import ThemedIcon from '@/components/ThemedIcon';
import { defaultPageSize, organizationRoleLink } from '@/consts';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import Table from '@/ds-components/Table';
import TablePlaceholder from '@/ds-components/Table/TablePlaceholder';
import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
import useDocumentationUrl from '@/hooks/use-documentation-url';
import useSearchParametersWatcher from '@/hooks/use-search-parameters-watcher';
import { buildUrl } from '@/utils/url';

import * as styles from './index.module.scss';

function OrgRoles() {
const { getDocumentationUrl } = useDocumentationUrl();

const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});

const { data, error, mutate, isLoading } = useSWR<
[OrganizationRoleWithScopes[], number],
RequestError
>(
buildUrl('api/organization-roles', {
page: String(page),
page_size: String(defaultPageSize),
})
);

const [orgRoles, totalCount] = data ?? [];

return (
<Table
rowGroups={[{ key: 'orgRoles', data: orgRoles }]}
rowIndexKey="id"
columns={[
{
title: <DynamicT forKey="organization_template.org_roles.org_role_column" />,
dataIndex: 'name',
colSpan: 4,
render: ({ name }) => {
return <ItemPreview title={name} icon={<ThemedIcon for={OrgRoleIcon} />} />;
},
},
{
title: <DynamicT forKey="organization_template.org_roles.permissions_column" />,
dataIndex: 'scopes',
colSpan: 12,
render: ({ scopes }) => {
return scopes.length === 0 ? (
'-'
) : (
<div className={styles.permissions}>
{scopes.map(({ id, name }) => (
<Tag key={id} variant="cell">
<Breakable>{name}</Breakable>
</Tag>
))}
</div>
);
},
},
]}
filter={
<div className={styles.filter}>
<Button
title="organization_template.org_roles.create_org_roles"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
// Todo @xiaoyijun implment create org role

Check warning on line 83 in packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx#L83

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implment create org role'.
}}
/>
</div>
}
placeholder={
<TablePlaceholder
image={<RolesEmpty />}
imageDark={<RolesEmptyDark />}
title="organization_template.org_roles.placeholder_title"
description="organization_template.org_roles.placeholder_description"
learnMoreLink={{
href: getDocumentationUrl(organizationRoleLink),
targetBlank: 'noopener',
}}
action={
<Button
title="organization_template.org_roles.create_org_roles"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
// Todo @xiaoyijun implment create org role

Check warning on line 105 in packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/OrganizationTemplate/OrgRoles/index.tsx#L105

[no-warning-comments] Unexpected 'todo' comment: 'Todo @xiaoyijun implment create org role'.
}}
/>
}
/>
}
pagination={{
page,
totalCount,
pageSize: defaultPageSize,
onChange: (page) => {
updateSearchParameters({ page });
},
}}
isLoading={isLoading}
errorMessage={error?.body?.message ?? error?.message}
onRetry={async () => mutate(undefined, true)}
/>
);
}

export default OrgRoles;
Loading
Loading