mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-24 17:08:28 +00:00
✨ Feat : Add New roles and policy UI - Part 3 (#6826)
* ✨ Feat : Add New roles and policy UI - Part 3
* Change rules styling
* Add delete action for policy details page
* Change background of the add forms
This commit is contained in:
parent
8691022d0f
commit
33ca751b58
@ -11,7 +11,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Card, Col, Form, Input, Row, Select, Space } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@ -82,10 +92,13 @@ const AddPolicyPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]}>
|
<Row className="tw-bg-body-main tw-h-full" gutter={[16, 16]}>
|
||||||
<Col offset={5} span={14}>
|
<Col offset={5} span={14}>
|
||||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||||
<Card title="Add New Policy">
|
<Card>
|
||||||
|
<Typography.Paragraph className="tw-text-lg">
|
||||||
|
Add New Policy
|
||||||
|
</Typography.Paragraph>
|
||||||
<Form
|
<Form
|
||||||
data-testid="policy-form"
|
data-testid="policy-form"
|
||||||
id="policy-form"
|
id="policy-form"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.policies-detail {
|
.policies-detail {
|
||||||
.roles-list-table {
|
.list-table {
|
||||||
.ant-table-row .ant-table-cell:first-child,
|
.ant-table-row .ant-table-cell:first-child,
|
||||||
.ant-table-thead .ant-table-cell:first-child {
|
.ant-table-thead .ant-table-cell:first-child {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
@ -11,7 +11,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Card, Col, Empty, Row, Table, Tabs } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Empty,
|
||||||
|
Row,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
@ -28,16 +39,27 @@ import {
|
|||||||
GlobalSettingsMenuCategory,
|
GlobalSettingsMenuCategory,
|
||||||
} from '../../../constants/globalSettings.constants';
|
} from '../../../constants/globalSettings.constants';
|
||||||
import { EntityType } from '../../../enums/entity.enum';
|
import { EntityType } from '../../../enums/entity.enum';
|
||||||
import { Policy } from '../../../generated/entity/policies/policy';
|
import { Effect, Policy } from '../../../generated/entity/policies/policy';
|
||||||
import { EntityReference } from '../../../generated/type/entityReference';
|
import { EntityReference } from '../../../generated/type/entityReference';
|
||||||
import { getEntityName } from '../../../utils/CommonUtils';
|
import { getEntityName } from '../../../utils/CommonUtils';
|
||||||
import { getRoleWithFqnPath, getSettingPath } from '../../../utils/RouterUtils';
|
import {
|
||||||
|
getRoleWithFqnPath,
|
||||||
|
getSettingPath,
|
||||||
|
getTeamsWithFqnPath,
|
||||||
|
} from '../../../utils/RouterUtils';
|
||||||
|
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import './PoliciesDetail.less';
|
import './PoliciesDetail.less';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
const List = ({
|
||||||
|
list,
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
list: EntityReference[];
|
||||||
|
type: 'role' | 'team';
|
||||||
|
}) => {
|
||||||
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -45,13 +67,28 @@ const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: '200px',
|
width: '200px',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
render: (_, record) => (
|
render: (_, record) => {
|
||||||
<Link
|
let link = '';
|
||||||
className="hover:tw-underline tw-cursor-pointer"
|
switch (type) {
|
||||||
to={getRoleWithFqnPath(record.fullyQualifiedName || '')}>
|
case 'role':
|
||||||
{getEntityName(record)}
|
link = getRoleWithFqnPath(record.fullyQualifiedName || '');
|
||||||
</Link>
|
|
||||||
),
|
break;
|
||||||
|
case 'team':
|
||||||
|
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link className="hover:tw-underline tw-cursor-pointer" to={link}>
|
||||||
|
{getEntityName(record)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
@ -61,14 +98,27 @@ const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
|||||||
<RichTextEditorPreviewer markdown={record?.description || ''} />
|
<RichTextEditorPreviewer markdown={record?.description || ''} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Actions',
|
||||||
|
dataIndex: 'actions',
|
||||||
|
width: '80px',
|
||||||
|
key: 'actions',
|
||||||
|
render: () => {
|
||||||
|
return (
|
||||||
|
<Button type="text">
|
||||||
|
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
className="roles-list-table"
|
className="list-table"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={roles}
|
dataSource={list}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="middle"
|
size="middle"
|
||||||
/>
|
/>
|
||||||
@ -167,11 +217,51 @@ const PoliciesDetailPage = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{policy.rules.map((rule) => (
|
{policy.rules.map((rule) => (
|
||||||
<Col key={uniqueId()} span={8}>
|
<Col key={uniqueId()} span={24}>
|
||||||
<Card title={rule.name}>
|
<Card>
|
||||||
<RichTextEditorPreviewer
|
<Space
|
||||||
markdown={rule.description || ''}
|
align="baseline"
|
||||||
/>
|
className="tw-w-full tw-justify-between"
|
||||||
|
size={4}>
|
||||||
|
<Typography.Paragraph className="tw-font-medium tw-text-base">
|
||||||
|
{rule.name}
|
||||||
|
</Typography.Paragraph>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
checked={rule.effect === Effect.Allow}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<span className="tw-ml-1">Active</span>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<div className="tw-mb-3" data-testid="description">
|
||||||
|
<Typography.Text className="tw-text-grey-muted">
|
||||||
|
Description:
|
||||||
|
</Typography.Text>
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={rule.description || ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Space data-testid="resources" direction="vertical">
|
||||||
|
<Typography.Text className="tw-text-grey-muted tw-mb-0">
|
||||||
|
Resources:
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text>
|
||||||
|
{rule.resources?.join(', ')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space data-testid="operations" direction="vertical">
|
||||||
|
<Typography.Text className="tw-text-grey-muted">
|
||||||
|
Operations:
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text>
|
||||||
|
{rule.operations?.join(', ')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
@ -179,23 +269,13 @@ const PoliciesDetailPage = () => {
|
|||||||
)}
|
)}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane key="roles" tab="Roles">
|
<TabPane key="roles" tab="Roles">
|
||||||
<RolesList roles={policy.roles ?? []} />
|
<List list={policy.roles ?? []} type="role" />
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane key="teams" tab="Teams">
|
<TabPane key="teams" tab="Teams">
|
||||||
{isEmpty(policy.teams) ? (
|
{isEmpty(policy.teams) ? (
|
||||||
<Empty description="No teams found" />
|
<Empty description="No teams found" />
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<List list={policy.teams ?? []} type="team" />
|
||||||
{policy.teams?.map((team) => (
|
|
||||||
<Col key={uniqueId()} span={6}>
|
|
||||||
<Card title={getEntityName(team)}>
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={team.description || ''}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
)}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Space, Table } from 'antd';
|
import { Button, Popover, Space, Table, Tag } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { isUndefined, uniqueId } from 'lodash';
|
import { isUndefined, uniqueId } from 'lodash';
|
||||||
import React, { FC, useMemo, useState } from 'react';
|
import React, { FC, useMemo, useState } from 'react';
|
||||||
@ -22,6 +22,7 @@ import { EntityType } from '../../../enums/entity.enum';
|
|||||||
import { Policy } from '../../../generated/entity/policies/policy';
|
import { Policy } from '../../../generated/entity/policies/policy';
|
||||||
import { Paging } from '../../../generated/type/paging';
|
import { Paging } from '../../../generated/type/paging';
|
||||||
import { getEntityName } from '../../../utils/CommonUtils';
|
import { getEntityName } from '../../../utils/CommonUtils';
|
||||||
|
import { LIST_CAP } from '../../../utils/PermissionsUtils';
|
||||||
import {
|
import {
|
||||||
getPolicyWithFqnPath,
|
getPolicyWithFqnPath,
|
||||||
getRoleWithFqnPath,
|
getRoleWithFqnPath,
|
||||||
@ -61,18 +62,44 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
|
|||||||
{
|
{
|
||||||
title: 'Roles',
|
title: 'Roles',
|
||||||
dataIndex: 'roles',
|
dataIndex: 'roles',
|
||||||
width: '200px',
|
width: '250px',
|
||||||
key: 'roles',
|
key: 'roles',
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
const listLength = record.roles?.length ?? 0;
|
||||||
|
const hasMore = listLength > LIST_CAP;
|
||||||
|
|
||||||
return record.roles?.length ? (
|
return record.roles?.length ? (
|
||||||
<Space wrap size={4}>
|
<Space wrap size={4}>
|
||||||
{record.roles.map((role) => (
|
{record.roles.slice(0, LIST_CAP).map((role) => (
|
||||||
<Link
|
<Link
|
||||||
key={uniqueId()}
|
key={uniqueId()}
|
||||||
to={getRoleWithFqnPath(role.fullyQualifiedName || '')}>
|
to={getRoleWithFqnPath(role.fullyQualifiedName || '')}>
|
||||||
{getEntityName(role)}
|
{getEntityName(role)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
{hasMore && (
|
||||||
|
<Popover
|
||||||
|
className="tw-cursor-pointer"
|
||||||
|
content={
|
||||||
|
<Space wrap size={4}>
|
||||||
|
{record.roles.slice(LIST_CAP).map((role) => (
|
||||||
|
<Link
|
||||||
|
key={uniqueId()}
|
||||||
|
to={getRoleWithFqnPath(
|
||||||
|
role.fullyQualifiedName || ''
|
||||||
|
)}>
|
||||||
|
{getEntityName(role)}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
overlayClassName="tw-w-40 tw-text-center"
|
||||||
|
trigger="click">
|
||||||
|
<Tag className="tw-ml-1">{`+${
|
||||||
|
listLength - LIST_CAP
|
||||||
|
} more`}</Tag>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
'-- '
|
'-- '
|
||||||
|
@ -11,7 +11,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Card, Col, Form, Input, Row, Select, Space } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@ -90,10 +100,13 @@ const AddRolePage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]}>
|
<Row className="tw-bg-body-main tw-h-full" gutter={[16, 16]}>
|
||||||
<Col offset={5} span={14}>
|
<Col offset={5} span={14}>
|
||||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||||
<Card title="Add New Role">
|
<Card>
|
||||||
|
<Typography.Paragraph className="tw-text-lg">
|
||||||
|
Add New Role
|
||||||
|
</Typography.Paragraph>
|
||||||
<Form
|
<Form
|
||||||
data-testid="role-form"
|
data-testid="role-form"
|
||||||
id="role-form"
|
id="role-form"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.roles-detail {
|
.roles-detail {
|
||||||
.policies-list-table {
|
.list-table {
|
||||||
.ant-table-row .ant-table-cell:first-child,
|
.ant-table-row .ant-table-cell:first-child,
|
||||||
.ant-table-thead .ant-table-cell:first-child {
|
.ant-table-thead .ant-table-cell:first-child {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
|
@ -11,11 +11,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Card, Col, Empty, Row, Table, Tabs } from 'antd';
|
import { Button, Empty, Table, Tabs } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { isEmpty, uniqueId } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { EntityReference } from 'Models';
|
import { EntityReference } from 'Models';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
@ -24,6 +24,7 @@ import Description from '../../../components/common/description/Description';
|
|||||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
||||||
import Loader from '../../../components/Loader/Loader';
|
import Loader from '../../../components/Loader/Loader';
|
||||||
|
import { getUserPath } from '../../../constants/constants';
|
||||||
import {
|
import {
|
||||||
GlobalSettingOptions,
|
GlobalSettingOptions,
|
||||||
GlobalSettingsMenuCategory,
|
GlobalSettingsMenuCategory,
|
||||||
@ -34,13 +35,21 @@ import { getEntityName } from '../../../utils/CommonUtils';
|
|||||||
import {
|
import {
|
||||||
getPolicyWithFqnPath,
|
getPolicyWithFqnPath,
|
||||||
getSettingPath,
|
getSettingPath,
|
||||||
|
getTeamsWithFqnPath,
|
||||||
} from '../../../utils/RouterUtils';
|
} from '../../../utils/RouterUtils';
|
||||||
|
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import './RolesDetail.less';
|
import './RolesDetail.less';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
const List = ({
|
||||||
|
list,
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
list: EntityReference[];
|
||||||
|
type: 'policy' | 'team' | 'user';
|
||||||
|
}) => {
|
||||||
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -48,13 +57,32 @@ const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
width: '200px',
|
width: '200px',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
render: (_, record) => (
|
render: (_, record) => {
|
||||||
<Link
|
let link = '';
|
||||||
className="hover:tw-underline tw-cursor-pointer"
|
switch (type) {
|
||||||
to={getPolicyWithFqnPath(record.fullyQualifiedName || '')}>
|
case 'policy':
|
||||||
{getEntityName(record)}
|
link = getPolicyWithFqnPath(record.fullyQualifiedName || '');
|
||||||
</Link>
|
|
||||||
),
|
break;
|
||||||
|
case 'team':
|
||||||
|
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
link = getUserPath(record.fullyQualifiedName || '');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link className="hover:tw-underline tw-cursor-pointer" to={link}>
|
||||||
|
{getEntityName(record)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
@ -64,14 +92,27 @@ const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
|||||||
<RichTextEditorPreviewer markdown={record?.description || ''} />
|
<RichTextEditorPreviewer markdown={record?.description || ''} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Actions',
|
||||||
|
dataIndex: 'actions',
|
||||||
|
width: '80px',
|
||||||
|
key: 'actions',
|
||||||
|
render: () => {
|
||||||
|
return (
|
||||||
|
<Button type="text">
|
||||||
|
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
className="policies-list-table"
|
className="list-table"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={policies}
|
dataSource={list}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="middle"
|
size="middle"
|
||||||
/>
|
/>
|
||||||
@ -165,40 +206,20 @@ const RolesDetailPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Tabs defaultActiveKey="policies">
|
<Tabs defaultActiveKey="policies">
|
||||||
<TabPane key="policies" tab="Policies">
|
<TabPane key="policies" tab="Policies">
|
||||||
<PoliciesList policies={role.policies ?? []} />
|
<List list={role.policies ?? []} type="policy" />
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane key="teams" tab="Teams">
|
<TabPane key="teams" tab="Teams">
|
||||||
{isEmpty(role.teams) ? (
|
{isEmpty(role.teams) ? (
|
||||||
<Empty description="No teams found" />
|
<Empty description="No teams found" />
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<List list={role.teams ?? []} type="team" />
|
||||||
{role.teams?.map((team) => (
|
|
||||||
<Col key={uniqueId()} span={6}>
|
|
||||||
<Card title={getEntityName(team)}>
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={team.description || ''}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
)}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<TabPane key="users" tab="Users">
|
<TabPane key="users" tab="Users">
|
||||||
{isEmpty(role.users) ? (
|
{isEmpty(role.users) ? (
|
||||||
<Empty description="No users found" />
|
<Empty description="No users found" />
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<List list={role.users ?? []} type="user" />
|
||||||
{role.users?.map((user) => (
|
|
||||||
<Col key={uniqueId()} span={6}>
|
|
||||||
<Card title={getEntityName(user)}>
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={user.description || ''}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
)}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Space, Table } from 'antd';
|
import { Button, Popover, Space, Table, Tag } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { isUndefined, uniqueId } from 'lodash';
|
import { isUndefined, uniqueId } from 'lodash';
|
||||||
import React, { FC, useMemo, useState } from 'react';
|
import React, { FC, useMemo, useState } from 'react';
|
||||||
@ -22,6 +22,7 @@ import { EntityType } from '../../../enums/entity.enum';
|
|||||||
import { Role } from '../../../generated/entity/teams/role';
|
import { Role } from '../../../generated/entity/teams/role';
|
||||||
import { Paging } from '../../../generated/type/paging';
|
import { Paging } from '../../../generated/type/paging';
|
||||||
import { getEntityName } from '../../../utils/CommonUtils';
|
import { getEntityName } from '../../../utils/CommonUtils';
|
||||||
|
import { LIST_CAP } from '../../../utils/PermissionsUtils';
|
||||||
import {
|
import {
|
||||||
getPolicyWithFqnPath,
|
getPolicyWithFqnPath,
|
||||||
getRoleWithFqnPath,
|
getRoleWithFqnPath,
|
||||||
@ -62,18 +63,44 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
|
|||||||
{
|
{
|
||||||
title: 'Policies',
|
title: 'Policies',
|
||||||
dataIndex: 'policies',
|
dataIndex: 'policies',
|
||||||
width: '200px',
|
width: '250px',
|
||||||
key: 'policies',
|
key: 'policies',
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
const listLength = record.policies?.length ?? 0;
|
||||||
|
const hasMore = listLength > LIST_CAP;
|
||||||
|
|
||||||
return record.policies?.length ? (
|
return record.policies?.length ? (
|
||||||
<Space wrap size={4}>
|
<Space wrap size={4}>
|
||||||
{record.policies.map((policy) => (
|
{record.policies.slice(0, LIST_CAP).map((policy) => (
|
||||||
<Link
|
<Link
|
||||||
key={uniqueId()}
|
key={uniqueId()}
|
||||||
to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}>
|
to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}>
|
||||||
{getEntityName(policy)}
|
{getEntityName(policy)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
{hasMore && (
|
||||||
|
<Popover
|
||||||
|
className="tw-cursor-pointer"
|
||||||
|
content={
|
||||||
|
<Space wrap size={4}>
|
||||||
|
{record.policies.slice(LIST_CAP).map((policy) => (
|
||||||
|
<Link
|
||||||
|
key={uniqueId()}
|
||||||
|
to={getPolicyWithFqnPath(
|
||||||
|
policy.fullyQualifiedName || ''
|
||||||
|
)}>
|
||||||
|
{getEntityName(policy)}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
overlayClassName="tw-w-40 tw-text-center"
|
||||||
|
trigger="click">
|
||||||
|
<Tag className="tw-ml-1">{`+${
|
||||||
|
listLength - LIST_CAP
|
||||||
|
} more`}</Tag>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
'-- '
|
'-- '
|
||||||
|
@ -33,3 +33,5 @@ export const hasPemission = (
|
|||||||
|
|
||||||
return currentPermission?.access === Access.Allow;
|
return currentPermission?.access === Access.Allow;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LIST_CAP = 1;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user