Skip to content

Commit a15e87c

Browse files
feat: disable controls for users with viewer rights (#848)
1 parent b6b8d7f commit a15e87c

File tree

14 files changed

+118
-36
lines changed

14 files changed

+118
-36
lines changed

src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx

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

3-
import {Button} from '@gravity-ui/uikit';
4-
import type {ButtonProps} from '@gravity-ui/uikit';
3+
import {Button, Popover} from '@gravity-ui/uikit';
4+
import type {ButtonProps, PopoverProps} from '@gravity-ui/uikit';
55

66
import {CriticalActionDialog} from '../CriticalActionDialog';
77

@@ -13,6 +13,10 @@ interface ButtonWithConfirmDialogProps<T, K> {
1313
buttonDisabled?: ButtonProps['disabled'];
1414
buttonView?: ButtonProps['view'];
1515
buttonClassName?: ButtonProps['className'];
16+
withPopover?: boolean;
17+
popoverContent?: PopoverProps['content'];
18+
popoverPlacement?: PopoverProps['placement'];
19+
popoverDisabled?: PopoverProps['disabled'];
1620
}
1721

1822
export function ButtonWithConfirmDialog<T, K>({
@@ -23,6 +27,10 @@ export function ButtonWithConfirmDialog<T, K>({
2327
buttonDisabled = false,
2428
buttonView = 'action',
2529
buttonClassName,
30+
withPopover = false,
31+
popoverContent,
32+
popoverPlacement = 'right',
33+
popoverDisabled = true,
2634
}: ButtonWithConfirmDialogProps<T, K>) {
2735
const [isConfirmDialogVisible, setIsConfirmDialogVisible] = React.useState(false);
2836
const [buttonLoading, setButtonLoading] = React.useState(false);
@@ -50,6 +58,36 @@ export function ButtonWithConfirmDialog<T, K>({
5058
setButtonLoading(false);
5159
};
5260

61+
const renderButton = () => {
62+
return (
63+
<Button
64+
onClick={() => setIsConfirmDialogVisible(true)}
65+
view={buttonView}
66+
disabled={buttonDisabled}
67+
loading={!buttonDisabled && buttonLoading}
68+
className={buttonClassName}
69+
>
70+
{children}
71+
</Button>
72+
);
73+
};
74+
75+
const renderContent = () => {
76+
if (withPopover) {
77+
return (
78+
<Popover
79+
content={popoverContent}
80+
placement={popoverPlacement}
81+
disabled={popoverDisabled}
82+
>
83+
{renderButton()}
84+
</Popover>
85+
);
86+
}
87+
88+
return renderButton();
89+
};
90+
5391
return (
5492
<React.Fragment>
5593
<CriticalActionDialog
@@ -62,15 +100,7 @@ export function ButtonWithConfirmDialog<T, K>({
62100
setIsConfirmDialogVisible(false);
63101
}}
64102
/>
65-
<Button
66-
onClick={() => setIsConfirmDialogVisible(true)}
67-
view={buttonView}
68-
disabled={buttonDisabled}
69-
loading={!buttonDisabled && buttonLoading}
70-
className={buttonClassName}
71-
>
72-
{children}
73-
</Button>
103+
{renderContent()}
74104
</React.Fragment>
75105
);
76106
}

src/containers/PDiskPage/PDiskPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function PDiskPage() {
2929
const dispatch = useTypedDispatch();
3030

3131
const nodesMap = useTypedSelector(selectNodesMap);
32+
const {isUserAllowedToMakeChanges} = useTypedSelector((state) => state.authentication);
3233

3334
const [{nodeId, pDiskId}] = useQueryParams({
3435
nodeId: StringParam,
@@ -117,9 +118,12 @@ export function PDiskPage() {
117118
<ButtonWithConfirmDialog
118119
onConfirmAction={handleRestart}
119120
onConfirmActionSuccess={handleAfterRestart}
120-
buttonDisabled={!nodeId || !pDiskId}
121+
buttonDisabled={!nodeId || !pDiskId || !isUserAllowedToMakeChanges}
121122
buttonView="normal"
122123
dialogContent={pDiskPageKeyset('restart-pdisk-dialog')}
124+
withPopover
125+
popoverContent={pDiskPageKeyset('restart-pdisk-not-allowed')}
126+
popoverDisabled={isUserAllowedToMakeChanges}
123127
>
124128
<Icon data={ArrowRotateLeft} />
125129
{pDiskPageKeyset('restart-pdisk-button')}

src/containers/PDiskPage/i18n/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"node": "Node",
66

77
"restart-pdisk-button": "Restart PDisk",
8-
"restart-pdisk-dialog": "PDisk will be restarted. Do you want to proceed?"
8+
"restart-pdisk-dialog": "PDisk will be restarted. Do you want to proceed?",
9+
"restart-pdisk-not-allowed": "You don't have enough rights to restart PDisk"
910
}

src/containers/Tablet/TabletControls/TabletControls.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import {ButtonWithConfirmDialog} from '../../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog';
44
import {ETabletState} from '../../../types/api/tablet';
55
import type {TTabletStateInfo} from '../../../types/api/tablet';
6+
import {useTypedSelector} from '../../../utils/hooks';
67
import {b} from '../Tablet';
78
import i18n from '../i18n';
89

@@ -14,6 +15,8 @@ interface TabletControlsProps {
1415
export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
1516
const {TabletId, HiveId} = tablet;
1617

18+
const {isUserAllowedToMakeChanges} = useTypedSelector((state) => state.authentication);
19+
1720
const _onKillClick = () => {
1821
return window.api.killTablet(TabletId);
1922
};
@@ -43,7 +46,11 @@ export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
4346
onConfirmAction={_onKillClick}
4447
onConfirmActionSuccess={fetchData}
4548
buttonClassName={b('control')}
46-
buttonDisabled={isDisabledRestart}
49+
buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges}
50+
withPopover
51+
popoverContent={i18n('controls.kill-not-allowed')}
52+
popoverPlacement={'bottom'}
53+
popoverDisabled={isUserAllowedToMakeChanges}
4754
>
4855
{i18n('controls.kill')}
4956
</ButtonWithConfirmDialog>
@@ -54,7 +61,11 @@ export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
5461
onConfirmAction={_onStopClick}
5562
onConfirmActionSuccess={fetchData}
5663
buttonClassName={b('control')}
57-
buttonDisabled={isDisabledStop}
64+
buttonDisabled={isDisabledStop || !isUserAllowedToMakeChanges}
65+
withPopover
66+
popoverContent={i18n('controls.stop-not-allowed')}
67+
popoverPlacement={'bottom'}
68+
popoverDisabled={isUserAllowedToMakeChanges}
5869
>
5970
{i18n('controls.stop')}
6071
</ButtonWithConfirmDialog>
@@ -63,7 +74,11 @@ export const TabletControls = ({tablet, fetchData}: TabletControlsProps) => {
6374
onConfirmAction={_onResumeClick}
6475
onConfirmActionSuccess={fetchData}
6576
buttonClassName={b('control')}
66-
buttonDisabled={isDisabledResume}
77+
buttonDisabled={isDisabledResume || !isUserAllowedToMakeChanges}
78+
withPopover
79+
popoverContent={i18n('controls.resume-not-allowed')}
80+
popoverPlacement={'bottom'}
81+
popoverDisabled={isUserAllowedToMakeChanges}
6782
>
6883
{i18n('controls.resume')}
6984
</ButtonWithConfirmDialog>

src/containers/Tablet/i18n/en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
{
22
"tablet.header": "Tablet",
3+
34
"controls.kill": "Restart",
45
"controls.stop": "Stop",
56
"controls.resume": "Resume",
7+
8+
"controls.kill-not-allowed": "You don't have enough rights to restart tablet",
9+
"controls.stop-not-allowed": "You don't have enough rights to stop tablet",
10+
"controls.resume-not-allowed": "You don't have enough rights to resume tablet",
11+
612
"dialog.kill": "The tablet will be restarted. Do you want to proceed?",
713
"dialog.stop": "The tablet will be stopped. Do you want to proceed?",
814
"dialog.resume": "The tablet will be resumed. Do you want to proceed?",
15+
916
"emptyState": "The tablet was not found"
1017
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {registerKeysets} from '../../../utils/i18n';
22

33
import en from './en.json';
4-
import ru from './ru.json';
54

65
const COMPONENT = 'ydb-tablet-page';
76

8-
export default registerKeysets(COMPONENT, {en, ru});
7+
export default registerKeysets(COMPONENT, {en});

src/containers/Tablet/i18n/ru.json

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

src/containers/Tablets/Tablets.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ const columns: DataTableColumn<TTabletStateInfo & {fqdn?: string}>[] = [
116116
function TabletActions(tablet: TTabletStateInfo) {
117117
const isDisabledRestart = tablet.State === ETabletState.Stopped;
118118
const dispatch = useTypedDispatch();
119+
const {isUserAllowedToMakeChanges} = useTypedSelector((state) => state.authentication);
120+
119121
return (
120122
<ButtonWithConfirmDialog
121123
buttonView="outlined"
@@ -126,7 +128,10 @@ function TabletActions(tablet: TTabletStateInfo) {
126128
onConfirmActionSuccess={() => {
127129
dispatch(tabletsApi.util.invalidateTags(['All']));
128130
}}
129-
buttonDisabled={isDisabledRestart}
131+
buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges}
132+
withPopover
133+
popoverContent={i18n('controls.kill-not-allowed')}
134+
popoverDisabled={isUserAllowedToMakeChanges}
130135
>
131136
<Icon data={ArrowsRotateRight} />
132137
</ButtonWithConfirmDialog>

src/containers/Tablets/i18n/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
"Node FQDN": "Node FQDN",
88
"Generation": "Generation",
99
"Uptime": "Uptime",
10-
"dialog.kill": "The tablet will be restarted. Do you want to proceed?"
10+
"dialog.kill": "The tablet will be restarted. Do you want to proceed?",
11+
"controls.kill-not-allowed": "You don't have enough rights to restart tablet"
1112
}

src/containers/VDiskPage/VDiskPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function VDiskPage() {
3333
const dispatch = useTypedDispatch();
3434

3535
const nodesMap = useTypedSelector(selectNodesMap);
36+
const {isUserAllowedToMakeChanges} = useTypedSelector((state) => state.authentication);
3637

3738
const [{nodeId, pDiskId, vDiskSlotId}] = useQueryParams({
3839
nodeId: StringParam,
@@ -129,9 +130,12 @@ export function VDiskPage() {
129130
<ButtonWithConfirmDialog
130131
onConfirmAction={handleEvictVDisk}
131132
onConfirmActionSuccess={handleAfterEvictVDisk}
132-
buttonDisabled={!VDiskId}
133+
buttonDisabled={!VDiskId || !isUserAllowedToMakeChanges}
133134
buttonView="normal"
134135
dialogContent={vDiskPageKeyset('evict-vdisk-dialog')}
136+
withPopover
137+
popoverContent={vDiskPageKeyset('evict-vdisk-not-allowed')}
138+
popoverDisabled={isUserAllowedToMakeChanges}
135139
>
136140
<Icon data={ArrowsOppositeToDots} />
137141
{vDiskPageKeyset('evict-vdisk-button')}

0 commit comments

Comments
 (0)