Skip to content

Commit 0c89baa

Browse files
authored
feat: manage schema object permissions (#2398)
1 parent 25b7bf9 commit 0c89baa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2643
-497
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.ydb-subject-with-avatar {
2+
&__avatar-wrapper {
3+
position: relative;
4+
}
5+
6+
&__subject {
7+
overflow: hidden;
8+
9+
text-overflow: ellipsis;
10+
}
11+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {Avatar, Flex, Text} from '@gravity-ui/uikit';
2+
3+
import {cn} from '../../utils/cn';
4+
5+
export const block = cn('ydb-subject-with-avatar');
6+
7+
import './SubjectWithAvatar.scss';
8+
9+
interface SubjectProps {
10+
subject: string;
11+
title?: string;
12+
renderIcon?: () => React.ReactNode;
13+
}
14+
15+
export function SubjectWithAvatar({subject, title, renderIcon}: SubjectProps) {
16+
return (
17+
<Flex gap={2} alignItems="center">
18+
<div className={block('avatar-wrapper')}>
19+
<Avatar theme="brand" text={subject} title={subject} />
20+
{renderIcon?.()}
21+
</div>
22+
<Flex direction="column" overflow="hidden">
23+
<Text variant="body-2" className={block('subject')}>
24+
{subject}
25+
</Text>
26+
{title && (
27+
<Text variant="body-2" color="secondary">
28+
{title}
29+
</Text>
30+
)}
31+
</Flex>
32+
</Flex>
33+
);
34+
}

src/containers/Tenant/Acl/Acl.scss

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/containers/Tenant/Acl/Acl.tsx

Lines changed: 23 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,208 +1,32 @@
1-
import React from 'react';
1+
import {Button, Flex, Icon, Text} from '@gravity-ui/uikit';
22

3-
import {DefinitionList} from '@gravity-ui/components';
4-
import type {DefinitionListItem} from '@gravity-ui/components';
5-
import {SquareCheck} from '@gravity-ui/icons';
6-
import {Icon} from '@gravity-ui/uikit';
7-
8-
import {ResponseError} from '../../../components/Errors/ResponseError';
9-
import {Loader} from '../../../components/Loader';
10-
import {schemaAclApi} from '../../../store/reducers/schemaAcl/schemaAcl';
11-
import type {TACE} from '../../../types/api/acl';
12-
import {valueIsDefined} from '../../../utils';
13-
import {cn} from '../../../utils/cn';
3+
import {
4+
TENANT_DIAGNOSTICS_TABS_IDS,
5+
TENANT_PAGES_IDS,
6+
} from '../../../store/reducers/tenant/constants';
7+
import {setDiagnosticsTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
8+
import {useTypedDispatch} from '../../../utils/hooks';
149

1510
import i18n from './i18n';
1611

17-
import './Acl.scss';
18-
19-
const b = cn('ydb-acl');
20-
21-
const prepareLogin = (value: string | undefined) => {
22-
if (value && value.endsWith('@staff') && !value.startsWith('svc_')) {
23-
const login = value.split('@')[0];
24-
return login;
25-
}
26-
27-
return value;
28-
};
29-
30-
const aclParams = ['access', 'type', 'inheritance'] as const;
31-
32-
type AclParameter = (typeof aclParams)[number];
33-
34-
const aclParamToName: Record<AclParameter, string> = {
35-
access: 'Access',
36-
type: 'Access type',
37-
inheritance: 'Inheritance type',
38-
};
39-
40-
const defaultInheritanceType = ['Object', 'Container'];
41-
const defaultAccessType = 'Allow';
42-
43-
const defaultInheritanceTypeSet = new Set(defaultInheritanceType);
44-
45-
function normalizeAcl(acl: TACE[]) {
46-
return acl.map((ace) => {
47-
const {AccessRules = [], AccessRights = [], AccessType, InheritanceType, Subject} = ace;
48-
const access = AccessRules.concat(AccessRights);
49-
//"Allow" is default access type. We want to show it only if it isn't default
50-
const type = AccessType === defaultAccessType ? undefined : AccessType;
51-
let inheritance;
52-
// ['Object', 'Container'] - is default inheritance type. We want to show it only if it isn't default
53-
if (
54-
InheritanceType?.length !== defaultInheritanceTypeSet.size ||
55-
InheritanceType.some((t) => !defaultInheritanceTypeSet.has(t))
56-
) {
57-
inheritance = InheritanceType;
58-
}
59-
return {
60-
access: access.length ? access : undefined,
61-
type,
62-
inheritance,
63-
Subject,
64-
};
65-
});
66-
}
12+
import ArrowRightFromSquareIcon from '@gravity-ui/icons/svgs/arrow-right-from-square.svg';
6713

68-
interface DefinitionValueProps {
69-
value: string | string[];
70-
}
14+
export const Acl = () => {
15+
const dispatch = useTypedDispatch();
7116

72-
function DefinitionValue({value}: DefinitionValueProps) {
73-
const normalizedValue = typeof value === 'string' ? [value] : value;
7417
return (
75-
<div className={b('definition-content')}>
76-
{normalizedValue.map((el) => (
77-
<span key={el}>{el}</span>
78-
))}
79-
</div>
80-
);
81-
}
82-
83-
function getAclListItems(acl?: TACE[]): DefinitionListItem[] {
84-
if (!acl || !acl.length) {
85-
return [];
86-
}
87-
88-
const normalizedAcl = normalizeAcl(acl);
89-
90-
return normalizedAcl.map(({Subject, ...data}) => {
91-
const definedDataEntries = Object.entries(data).filter(([_key, value]) =>
92-
Boolean(value),
93-
) as [AclParameter, string | string[]][];
94-
95-
if (definedDataEntries.length === 1 && definedDataEntries[0][0] === 'access') {
96-
return {
97-
name: Subject,
98-
content: <DefinitionValue value={definedDataEntries[0][1]} />,
99-
multilineName: true,
100-
};
101-
}
102-
return {
103-
label: <span className={b('group-label')}>{Subject}</span>,
104-
items: aclParams
105-
.map((key) => {
106-
const value = data[key];
107-
if (value) {
108-
return {
109-
name: aclParamToName[key],
110-
content: <DefinitionValue value={value} />,
111-
multilineName: true,
112-
};
113-
}
114-
return undefined;
115-
})
116-
.filter(valueIsDefined),
117-
};
118-
});
119-
}
120-
121-
function getOwnerItem(owner?: string): DefinitionListItem[] {
122-
const preparedOwner = prepareLogin(owner);
123-
if (!preparedOwner) {
124-
return [];
125-
}
126-
return [
127-
{
128-
name: preparedOwner,
129-
content: i18n('title_owner'),
130-
multilineName: true,
131-
},
132-
];
133-
}
134-
135-
function getInterruptInheritanceItem(flag?: boolean): DefinitionListItem[] {
136-
if (!flag) {
137-
return [];
138-
}
139-
return [
140-
{
141-
name: i18n('title_interupt-inheritance'),
142-
content: <Icon data={SquareCheck} size={20} />,
143-
multilineName: true,
144-
},
145-
];
146-
}
147-
148-
export const Acl = ({path, database}: {path: string; database: string}) => {
149-
const {currentData, isFetching, error} = schemaAclApi.useGetSchemaAclQuery({path, database});
150-
151-
const loading = isFetching && !currentData;
152-
153-
const {acl, effectiveAcl, owner, interruptInheritance} = currentData || {};
154-
155-
const aclListItems = getAclListItems(acl);
156-
const effectiveAclListItems = getAclListItems(effectiveAcl);
157-
158-
const ownerItem = getOwnerItem(owner);
159-
160-
const interruptInheritanceItem = getInterruptInheritanceItem(interruptInheritance);
161-
162-
if (loading) {
163-
return <Loader />;
164-
}
165-
166-
if (error) {
167-
return <ResponseError error={error} />;
168-
}
169-
170-
if (!acl && !owner && !effectiveAcl) {
171-
return <React.Fragment>{i18n('description_empty')}</React.Fragment>;
172-
}
173-
174-
const accessRightsItems = ownerItem.concat(aclListItems);
175-
176-
return (
177-
<div className={b()}>
178-
<AclDefinitionList items={interruptInheritanceItem} />
179-
<AclDefinitionList items={accessRightsItems} title={i18n('title_rights')} />
180-
<AclDefinitionList
181-
items={effectiveAclListItems}
182-
title={i18n('title_effective-rights')}
183-
/>
184-
</div>
18+
<Flex gap={2} alignItems="center">
19+
<Text variant="body-2">{i18n('description_section-moved')}</Text>
20+
<Button
21+
title={i18n('action-open-in-diagnostics')}
22+
onClick={() => {
23+
dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics));
24+
dispatch(setDiagnosticsTab(TENANT_DIAGNOSTICS_TABS_IDS.access));
25+
}}
26+
size="s"
27+
>
28+
<Icon data={ArrowRightFromSquareIcon} size={14} />
29+
</Button>
30+
</Flex>
18531
);
18632
};
187-
188-
interface AclDefinitionListProps {
189-
items: DefinitionListItem[];
190-
title?: string;
191-
}
192-
193-
function AclDefinitionList({items, title}: AclDefinitionListProps) {
194-
if (!items.length) {
195-
return null;
196-
}
197-
return (
198-
<React.Fragment>
199-
{title && <div className={b('list-title')}>{title}</div>}
200-
<DefinitionList
201-
items={items}
202-
nameMaxWidth={200}
203-
className={b('result', {'no-title': !title})}
204-
responsive
205-
/>
206-
</React.Fragment>
207-
);
208-
}
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
2-
"title_rights": "Access Rights",
3-
"title_effective-rights": "Effective Access Rights",
4-
"title_owner": "Owner",
5-
"title_interupt-inheritance": "Interrupt inheritance",
6-
"description_empty": "No Acl data"
2+
"description_section-moved": "Section was moved to Diagnostics",
3+
"action-open-in-diagnostics": "Open in Diagnostics"
74
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
.ydb-access-rights {
2+
$block: &;
3+
&__header {
4+
position: sticky;
5+
left: 0;
6+
7+
margin-bottom: var(--g-spacing-3);
8+
}
9+
&__owner-card {
10+
width: max-content;
11+
padding: var(--g-spacing-2) var(--g-spacing-3);
12+
}
13+
14+
&__icon-wrapper {
15+
position: absolute;
16+
right: -2px;
17+
bottom: -2px;
18+
19+
height: 16px;
20+
21+
color: var(--g-color-base-warning-heavy);
22+
border-radius: 50%;
23+
background: var(--g-color-base-background);
24+
aspect-ratio: 1;
25+
}
26+
&__owner-divider {
27+
height: 24px;
28+
}
29+
&__owner-description {
30+
max-width: 391px;
31+
}
32+
&__dialog-content-wrapper {
33+
position: relative;
34+
35+
height: 46px;
36+
}
37+
&__dialog-error {
38+
position: absolute;
39+
bottom: 0;
40+
left: 0;
41+
42+
overflow: hidden;
43+
44+
max-width: 100%;
45+
46+
white-space: nowrap;
47+
text-overflow: ellipsis;
48+
}
49+
50+
&__note {
51+
display: flex;
52+
.g-help-mark__button {
53+
display: flex;
54+
}
55+
}
56+
&__rights-wrapper {
57+
position: relative;
58+
59+
width: 100%;
60+
height: 100%;
61+
}
62+
&__rights-actions {
63+
position: absolute;
64+
right: 0;
65+
66+
visibility: hidden;
67+
68+
height: 100%;
69+
padding-left: var(--g-spacing-2);
70+
71+
background-color: var(--ydb-data-table-color-hover);
72+
}
73+
&__rights-table {
74+
.data-table__row:hover {
75+
#{$block}__rights-actions {
76+
visibility: visible;
77+
}
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)