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:
Sachin Chaurasiya 2022-08-19 22:31:48 +05:30 committed by GitHub
parent 8691022d0f
commit 33ca751b58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 262 additions and 79 deletions

View File

@ -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"

View File

@ -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;

View File

@ -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>

View File

@ -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>
) : (
'-- '

View File

@ -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"

View File

@ -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;

View File

@ -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>

View File

@ -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>
) : (
'-- '

View File

@ -33,3 +33,5 @@ export const hasPemission = (
return currentPermission?.access === Access.Allow;
};
export const LIST_CAP = 1;