Skip to content

Commit 46aff45

Browse files
authored
feat(Acl): improve view (#955)
1 parent 6fbd52c commit 46aff45

File tree

8 files changed

+144
-92
lines changed

8 files changed

+144
-92
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@bem-react/classname": "^1.6.0",
1414
"@gravity-ui/axios-wrapper": "^1.4.1",
1515
"@gravity-ui/chartkit": "^5.5.0",
16-
"@gravity-ui/components": "^3.6.2",
16+
"@gravity-ui/components": "^3.7.0",
1717
"@gravity-ui/date-utils": "^2.4.0",
1818
"@gravity-ui/i18n": "^1.5.0",
1919
"@gravity-ui/icons": "^2.9.1",

src/containers/Tenant/Acl/Acl.scss

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
@import '../../../styles/mixins.scss';
22

33
.ydb-acl {
4-
&__result {
5-
align-self: flex-start;
6-
}
7-
4+
width: 100%;
85
&__owner-container {
9-
position: sticky;
10-
z-index: 2;
11-
top: 0;
12-
6+
padding-bottom: 25px;
7+
}
8+
&__result {
139
padding-bottom: 16px;
10+
}
1411

15-
background-color: var(--g-color-base-background);
12+
&__owner {
13+
font-weight: 600;
14+
}
15+
&__definition-content {
16+
display: flex;
17+
flex-direction: column;
18+
}
19+
&__group-label {
20+
@include subheader-2-typography();
1621
}
1722
}

src/containers/Tenant/Acl/Acl.tsx

Lines changed: 116 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import React from 'react';
22

3-
import type {Column} from '@gravity-ui/react-data-table';
3+
import type {DefinitionListItem} from '@gravity-ui/components';
4+
import {DefinitionList} from '@gravity-ui/components';
5+
//TODO: fix import
6+
import type {DefinitionListSingleItem} from '@gravity-ui/components/build/esm/components/DefinitionList/types';
47

58
import {ResponseError} from '../../../components/Errors/ResponseError';
69
import {Loader} from '../../../components/Loader';
7-
import {ResizeableDataTable} from '../../../components/ResizeableDataTable/ResizeableDataTable';
810
import {schemaAclApi} from '../../../store/reducers/schemaAcl/schemaAcl';
911
import type {TACE} from '../../../types/api/acl';
1012
import {cn} from '../../../utils/cn';
11-
import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
1213
import i18n from '../i18n';
1314

1415
import './Acl.scss';
1516

1617
const b = cn('ydb-acl');
1718

18-
const ACL_COLUMNS_WIDTH_LS_KEY = 'aclTableColumnsWidth';
19-
20-
const TABLE_SETTINGS = {
21-
...DEFAULT_TABLE_SETTINGS,
22-
dynamicRender: false,
23-
stickyTop: 36,
24-
};
25-
2619
const prepareLogin = (value: string | undefined) => {
2720
if (value && value.endsWith('@staff') && !value.startsWith('svc_')) {
2821
const login = value.split('@')[0];
@@ -32,76 +25,117 @@ const prepareLogin = (value: string | undefined) => {
3225
return value;
3326
};
3427

35-
const columns: Column<TACE>[] = [
36-
{
37-
name: 'AccessType',
38-
header: 'Access Type',
39-
sortable: false,
40-
render: ({row}) => row.AccessType,
41-
},
42-
{
43-
name: 'AccessRights',
44-
header: 'Access Rights',
45-
render: ({row}) => {
46-
return row.AccessRights?.map((item, index) => {
47-
return <div key={index}>{item}</div>;
48-
});
49-
},
50-
sortable: false,
51-
},
52-
{
53-
name: 'Subject',
54-
sortable: false,
55-
render: ({row}) => {
56-
return prepareLogin(row.Subject);
57-
},
58-
width: 140,
59-
},
60-
{
61-
name: 'InheritanceType',
62-
header: 'Inheritance Type',
63-
render: ({row}) => {
64-
return row.InheritanceType?.map((item, index) => {
65-
return <div key={index}>{item}</div>;
66-
});
28+
const aclParams = ['access', 'type', 'inheritance'] as const;
29+
30+
type AclParameter = (typeof aclParams)[number];
31+
32+
const aclParamToName: Record<AclParameter, string> = {
33+
access: 'Access',
34+
type: 'Access type',
35+
inheritance: 'Inheritance type',
36+
};
37+
38+
const defaultInheritanceType = ['Object', 'Container'];
39+
const defaultAccessType = 'Allow';
40+
41+
const defaultInheritanceTypeSet = new Set(defaultInheritanceType);
42+
43+
function normalizeAcl(acl: TACE[]) {
44+
return acl.map((ace) => {
45+
const {AccessRules = [], AccessRights = [], AccessType, InheritanceType, Subject} = ace;
46+
const access = AccessRules.concat(AccessRights);
47+
//"Allow" is default access type. We want to show it only if it isn't default
48+
const type = AccessType === defaultAccessType ? undefined : AccessType;
49+
let inheritance;
50+
// ['Object', 'Container'] - is default inheritance type. We want to show it only if it isn't default
51+
if (
52+
InheritanceType?.length !== defaultInheritanceTypeSet.size ||
53+
InheritanceType.some((t) => !defaultInheritanceTypeSet.has(t))
54+
) {
55+
inheritance = InheritanceType;
56+
}
57+
return {
58+
access: access.length ? access : undefined,
59+
type,
60+
inheritance,
61+
Subject,
62+
};
63+
});
64+
}
65+
66+
interface DefinitionValueProps {
67+
value: string | string[];
68+
}
69+
70+
function DefinitionValue({value}: DefinitionValueProps) {
71+
const normalizedValue = typeof value === 'string' ? [value] : value;
72+
return (
73+
<div className={b('definition-content')}>
74+
{normalizedValue.map((el) => (
75+
<span key={el}>{el}</span>
76+
))}
77+
</div>
78+
);
79+
}
80+
81+
function getAclListItems(acl?: TACE[]): DefinitionListItem[] {
82+
if (!acl || !acl.length) {
83+
return [];
84+
}
85+
86+
const normalizedAcl = normalizeAcl(acl);
87+
88+
return normalizedAcl.map(({Subject, ...data}) => {
89+
const definedDataEntries = Object.entries(data).filter(([_key, value]) =>
90+
Boolean(value),
91+
) as [AclParameter, string | string[]][];
92+
93+
if (definedDataEntries.length === 1 && definedDataEntries[0][0] === 'access') {
94+
return {
95+
name: Subject,
96+
content: <DefinitionValue value={definedDataEntries[0][1]} />,
97+
};
98+
}
99+
return {
100+
label: <span className={b('group-label')}>{Subject}</span>,
101+
items: aclParams
102+
.map((key) => {
103+
const value = data[key];
104+
if (value) {
105+
return {
106+
name: aclParamToName[key],
107+
content: <DefinitionValue value={value} />,
108+
};
109+
}
110+
return undefined;
111+
})
112+
.filter(Boolean) as DefinitionListSingleItem[],
113+
};
114+
});
115+
}
116+
117+
function getOwnerItem(owner?: string) {
118+
const preparedOwner = prepareLogin(owner);
119+
if (!preparedOwner) {
120+
return [];
121+
}
122+
return [
123+
{
124+
name: <span className={b('owner')}>{preparedOwner}</span>,
125+
content: <span className={b('owner')}>{i18n('acl.owner')}</span>,
67126
},
68-
sortable: false,
69-
},
70-
];
127+
] as DefinitionListItem[];
128+
}
71129

72130
export const Acl = ({path}: {path: string}) => {
73131
const {currentData, isFetching, error} = schemaAclApi.useGetSchemaAclQuery({path});
74132

75133
const loading = isFetching && !currentData;
76134
const {acl, owner} = currentData || {};
77135

78-
const renderTable = () => {
79-
if (!acl || !acl.length) {
80-
return null;
81-
}
82-
83-
return (
84-
<ResizeableDataTable
85-
columnsWidthLSKey={ACL_COLUMNS_WIDTH_LS_KEY}
86-
columns={columns}
87-
data={acl}
88-
settings={TABLE_SETTINGS}
89-
/>
90-
);
91-
};
92-
93-
const renderOwner = () => {
94-
if (!owner) {
95-
return null;
96-
}
136+
const aclListItems = getAclListItems(acl);
97137

98-
return (
99-
<div className={b('owner-container')}>
100-
<span className={b('owner-label')}>{`${i18n('acl.owner')}: `}</span>
101-
{prepareLogin(owner)}
102-
</div>
103-
);
104-
};
138+
const ownerItem = getOwnerItem(owner);
105139

106140
if (loading) {
107141
return <Loader />;
@@ -117,10 +151,16 @@ export const Acl = ({path}: {path: string}) => {
117151

118152
return (
119153
<div className={b()}>
120-
<div className={b('result')}>
121-
{renderOwner()}
122-
{renderTable()}
123-
</div>
154+
{ownerItem.length ? (
155+
<DefinitionList
156+
items={ownerItem}
157+
nameMaxWidth={200}
158+
className={b('owner-container')}
159+
/>
160+
) : null}
161+
{aclListItems.length ? (
162+
<DefinitionList items={aclListItems} nameMaxWidth={200} className={b('result')} />
163+
) : null}
124164
</div>
125165
);
126166
};

src/services/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
287287
this.getPath('/viewer/json/acl'),
288288
{
289289
path,
290+
merge_rules: true,
290291
},
291292
{concurrentId: concurrentId || `getSchemaAcl`, requestConfig: {signal}},
292293
);

src/styles/mixins.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
line-height: var(--g-text-subheader-3-line-height);
3636
}
3737

38+
@mixin subheader-2-typography() {
39+
font-size: var(--g-text-subheader-2-font-size);
40+
line-height: var(--g-text-subheader-2-line-height);
41+
}
42+
3843
@mixin header-1-typography() {
3944
font-size: var(--g-text-header-1-font-size);
4045
line-height: var(--g-text-header-1-line-height);

src/styles/themes.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
.g-root {
99
--g-text-header-font-weight: 500;
10-
--g-text-subheader-font-weight: 500;
10+
--g-text-subheader-font-weight: 600;
1111
--g-text-display-font-weight: 500;
1212
--g-text-accent-font-weight: 500;
1313

src/types/api/acl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export interface TMetaCommonInfo {
1919
export interface TACE {
2020
AccessType: string;
2121
AccessRights?: string[];
22+
AccessRules?: string[];
2223
Subject: string;
2324
InheritanceType?: string[];
24-
AccessRule: string;
25+
AccessRule?: string;
2526
}

0 commit comments

Comments
 (0)