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.
|
||||
*/
|
||||
|
||||
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 React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@ -82,10 +92,13 @@ const AddPolicyPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Row className="tw-bg-body-main tw-h-full" gutter={[16, 16]}>
|
||||
<Col offset={5} span={14}>
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
<Card title="Add New Policy">
|
||||
<Card>
|
||||
<Typography.Paragraph className="tw-text-lg">
|
||||
Add New Policy
|
||||
</Typography.Paragraph>
|
||||
<Form
|
||||
data-testid="policy-form"
|
||||
id="policy-form"
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
.policies-detail {
|
||||
.roles-list-table {
|
||||
.list-table {
|
||||
.ant-table-row .ant-table-cell:first-child,
|
||||
.ant-table-thead .ant-table-cell:first-child {
|
||||
padding-left: 16px;
|
||||
|
@ -11,7 +11,18 @@
|
||||
* 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 { AxiosError } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
@ -28,16 +39,27 @@ import {
|
||||
GlobalSettingsMenuCategory,
|
||||
} from '../../../constants/globalSettings.constants';
|
||||
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 { 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 './PoliciesDetail.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
||||
const List = ({
|
||||
list,
|
||||
type,
|
||||
}: {
|
||||
list: EntityReference[];
|
||||
type: 'role' | 'team';
|
||||
}) => {
|
||||
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -45,13 +67,28 @@ const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
||||
dataIndex: 'name',
|
||||
width: '200px',
|
||||
key: 'name',
|
||||
render: (_, record) => (
|
||||
<Link
|
||||
className="hover:tw-underline tw-cursor-pointer"
|
||||
to={getRoleWithFqnPath(record.fullyQualifiedName || '')}>
|
||||
{getEntityName(record)}
|
||||
</Link>
|
||||
),
|
||||
render: (_, record) => {
|
||||
let link = '';
|
||||
switch (type) {
|
||||
case 'role':
|
||||
link = getRoleWithFqnPath(record.fullyQualifiedName || '');
|
||||
|
||||
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',
|
||||
@ -61,14 +98,27 @@ const RolesList = ({ roles }: { roles: EntityReference[] }) => {
|
||||
<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 (
|
||||
<Table
|
||||
className="roles-list-table"
|
||||
className="list-table"
|
||||
columns={columns}
|
||||
dataSource={roles}
|
||||
dataSource={list}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
/>
|
||||
@ -167,11 +217,51 @@ const PoliciesDetailPage = () => {
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{policy.rules.map((rule) => (
|
||||
<Col key={uniqueId()} span={8}>
|
||||
<Card title={rule.name}>
|
||||
<RichTextEditorPreviewer
|
||||
markdown={rule.description || ''}
|
||||
/>
|
||||
<Col key={uniqueId()} span={24}>
|
||||
<Card>
|
||||
<Space
|
||||
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>
|
||||
</Col>
|
||||
))}
|
||||
@ -179,23 +269,13 @@ const PoliciesDetailPage = () => {
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane key="roles" tab="Roles">
|
||||
<RolesList roles={policy.roles ?? []} />
|
||||
<List list={policy.roles ?? []} type="role" />
|
||||
</TabPane>
|
||||
<TabPane key="teams" tab="Teams">
|
||||
{isEmpty(policy.teams) ? (
|
||||
<Empty description="No teams found" />
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{policy.teams?.map((team) => (
|
||||
<Col key={uniqueId()} span={6}>
|
||||
<Card title={getEntityName(team)}>
|
||||
<RichTextEditorPreviewer
|
||||
markdown={team.description || ''}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<List list={policy.teams ?? []} type="team" />
|
||||
)}
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
@ -11,7 +11,7 @@
|
||||
* 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 { isUndefined, uniqueId } from 'lodash';
|
||||
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 { Paging } from '../../../generated/type/paging';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import { LIST_CAP } from '../../../utils/PermissionsUtils';
|
||||
import {
|
||||
getPolicyWithFqnPath,
|
||||
getRoleWithFqnPath,
|
||||
@ -61,18 +62,44 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
|
||||
{
|
||||
title: 'Roles',
|
||||
dataIndex: 'roles',
|
||||
width: '200px',
|
||||
width: '250px',
|
||||
key: 'roles',
|
||||
render: (_, record) => {
|
||||
const listLength = record.roles?.length ?? 0;
|
||||
const hasMore = listLength > LIST_CAP;
|
||||
|
||||
return record.roles?.length ? (
|
||||
<Space wrap size={4}>
|
||||
{record.roles.map((role) => (
|
||||
{record.roles.slice(0, LIST_CAP).map((role) => (
|
||||
<Link
|
||||
key={uniqueId()}
|
||||
to={getRoleWithFqnPath(role.fullyQualifiedName || '')}>
|
||||
{getEntityName(role)}
|
||||
</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>
|
||||
) : (
|
||||
'-- '
|
||||
|
@ -11,7 +11,17 @@
|
||||
* 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 React, { useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@ -90,10 +100,13 @@ const AddRolePage = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Row className="tw-bg-body-main tw-h-full" gutter={[16, 16]}>
|
||||
<Col offset={5} span={14}>
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
<Card title="Add New Role">
|
||||
<Card>
|
||||
<Typography.Paragraph className="tw-text-lg">
|
||||
Add New Role
|
||||
</Typography.Paragraph>
|
||||
<Form
|
||||
data-testid="role-form"
|
||||
id="role-form"
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
.roles-detail {
|
||||
.policies-list-table {
|
||||
.list-table {
|
||||
.ant-table-row .ant-table-cell:first-child,
|
||||
.ant-table-thead .ant-table-cell:first-child {
|
||||
padding-left: 16px;
|
||||
|
@ -11,11 +11,11 @@
|
||||
* 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 { AxiosError } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { EntityReference } from 'Models';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
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 TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import Loader from '../../../components/Loader/Loader';
|
||||
import { getUserPath } from '../../../constants/constants';
|
||||
import {
|
||||
GlobalSettingOptions,
|
||||
GlobalSettingsMenuCategory,
|
||||
@ -34,13 +35,21 @@ import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import {
|
||||
getPolicyWithFqnPath,
|
||||
getSettingPath,
|
||||
getTeamsWithFqnPath,
|
||||
} from '../../../utils/RouterUtils';
|
||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import './RolesDetail.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
||||
const List = ({
|
||||
list,
|
||||
type,
|
||||
}: {
|
||||
list: EntityReference[];
|
||||
type: 'policy' | 'team' | 'user';
|
||||
}) => {
|
||||
const columns: ColumnsType<EntityReference> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -48,13 +57,32 @@ const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
||||
dataIndex: 'name',
|
||||
width: '200px',
|
||||
key: 'name',
|
||||
render: (_, record) => (
|
||||
<Link
|
||||
className="hover:tw-underline tw-cursor-pointer"
|
||||
to={getPolicyWithFqnPath(record.fullyQualifiedName || '')}>
|
||||
{getEntityName(record)}
|
||||
</Link>
|
||||
),
|
||||
render: (_, record) => {
|
||||
let link = '';
|
||||
switch (type) {
|
||||
case 'policy':
|
||||
link = getPolicyWithFqnPath(record.fullyQualifiedName || '');
|
||||
|
||||
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',
|
||||
@ -64,14 +92,27 @@ const PoliciesList = ({ policies }: { policies: EntityReference[] }) => {
|
||||
<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 (
|
||||
<Table
|
||||
className="policies-list-table"
|
||||
className="list-table"
|
||||
columns={columns}
|
||||
dataSource={policies}
|
||||
dataSource={list}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
/>
|
||||
@ -165,40 +206,20 @@ const RolesDetailPage = () => {
|
||||
</div>
|
||||
<Tabs defaultActiveKey="policies">
|
||||
<TabPane key="policies" tab="Policies">
|
||||
<PoliciesList policies={role.policies ?? []} />
|
||||
<List list={role.policies ?? []} type="policy" />
|
||||
</TabPane>
|
||||
<TabPane key="teams" tab="Teams">
|
||||
{isEmpty(role.teams) ? (
|
||||
<Empty description="No teams found" />
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{role.teams?.map((team) => (
|
||||
<Col key={uniqueId()} span={6}>
|
||||
<Card title={getEntityName(team)}>
|
||||
<RichTextEditorPreviewer
|
||||
markdown={team.description || ''}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<List list={role.teams ?? []} type="team" />
|
||||
)}
|
||||
</TabPane>
|
||||
<TabPane key="users" tab="Users">
|
||||
{isEmpty(role.users) ? (
|
||||
<Empty description="No users found" />
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{role.users?.map((user) => (
|
||||
<Col key={uniqueId()} span={6}>
|
||||
<Card title={getEntityName(user)}>
|
||||
<RichTextEditorPreviewer
|
||||
markdown={user.description || ''}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<List list={role.users ?? []} type="user" />
|
||||
)}
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
@ -11,7 +11,7 @@
|
||||
* 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 { isUndefined, uniqueId } from 'lodash';
|
||||
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 { Paging } from '../../../generated/type/paging';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import { LIST_CAP } from '../../../utils/PermissionsUtils';
|
||||
import {
|
||||
getPolicyWithFqnPath,
|
||||
getRoleWithFqnPath,
|
||||
@ -62,18 +63,44 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
|
||||
{
|
||||
title: 'Policies',
|
||||
dataIndex: 'policies',
|
||||
width: '200px',
|
||||
width: '250px',
|
||||
key: 'policies',
|
||||
render: (_, record) => {
|
||||
const listLength = record.policies?.length ?? 0;
|
||||
const hasMore = listLength > LIST_CAP;
|
||||
|
||||
return record.policies?.length ? (
|
||||
<Space wrap size={4}>
|
||||
{record.policies.map((policy) => (
|
||||
{record.policies.slice(0, LIST_CAP).map((policy) => (
|
||||
<Link
|
||||
key={uniqueId()}
|
||||
to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}>
|
||||
{getEntityName(policy)}
|
||||
</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>
|
||||
) : (
|
||||
'-- '
|
||||
|
@ -33,3 +33,5 @@ export const hasPemission = (
|
||||
|
||||
return currentPermission?.access === Access.Allow;
|
||||
};
|
||||
|
||||
export const LIST_CAP = 1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user