Skip to content

Commit 6213ed7

Browse files
authored
fix: show structure tab and PDisk/VDisk if disk's new API is absent (#1338)
1 parent 5402c86 commit 6213ed7

File tree

10 files changed

+161
-46
lines changed

10 files changed

+161
-46
lines changed

src/components/PDiskInfo/PDiskInfo.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {Flex} from '@gravity-ui/uikit';
22

33
import {getPDiskPagePath} from '../../routes';
44
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
5-
import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks';
65
import {valueIsDefined} from '../../utils';
76
import {formatBytes} from '../../utils/bytesParsers';
87
import {cn} from '../../utils/cn';
@@ -191,12 +190,11 @@ export function PDiskInfo<T extends PreparedPDisk>({
191190
className,
192191
}: PDiskInfoProps<T>) {
193192
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
194-
const diskPagesAvailable = useDiskPagesAvailable();
195193

196194
const [generalInfo, statusInfo, spaceInfo, additionalInfo] = getPDiskInfo({
197195
pDisk,
198196
nodeId,
199-
withPDiskPageLink: withPDiskPageLink && diskPagesAvailable,
197+
withPDiskPageLink,
200198
isUserAllowedToMakeChanges,
201199
});
202200

src/components/VDiskInfo/VDiskInfo.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React from 'react';
22

33
import {getVDiskPagePath} from '../../routes';
44
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
5-
import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks';
65
import {valueIsDefined} from '../../utils';
76
import {cn} from '../../utils/cn';
87
import {formatStorageValuesToGb, stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters';
@@ -36,7 +35,6 @@ export function VDiskInfo<T extends PreparedVDisk>({
3635
withTitle,
3736
...infoViewerProps
3837
}: VDiskInfoProps<T>) {
39-
const diskPagesAvailable = useDiskPagesAvailable();
4038
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
4139

4240
const {
@@ -154,7 +152,7 @@ export function VDiskInfo<T extends PreparedVDisk>({
154152
if (diskParamsDefined) {
155153
const links: React.ReactNode[] = [];
156154

157-
if (withVDiskPageLink && diskPagesAvailable) {
155+
if (withVDiskPageLink) {
158156
const vDiskPagePath = getVDiskPagePath(VDiskSlotId, PDiskId, NodeId);
159157
links.push(
160158
<LinkWithIcon

src/containers/Node/Node.tsx

+17-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import {ResponseError} from '../../components/Errors/ResponseError';
1010
import {FullNodeViewer} from '../../components/FullNodeViewer/FullNodeViewer';
1111
import {Loader} from '../../components/Loader';
1212
import routes, {createHref, parseQuery} from '../../routes';
13+
import {
14+
useCapabilitiesLoaded,
15+
useDiskPagesAvailable,
16+
} from '../../store/reducers/capabilities/hooks';
1317
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
1418
import {nodeApi} from '../../store/reducers/node/node';
1519
import type {AdditionalNodesProps} from '../../types/additionalProps';
@@ -18,7 +22,8 @@ import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks';
1822
import {StorageWrapper} from '../Storage/StorageWrapper';
1923
import {Tablets} from '../Tablets';
2024

21-
import {NODE_PAGES, OVERVIEW, STORAGE, TABLETS} from './NodePages';
25+
import {NODE_PAGES, OVERVIEW, STORAGE, STRUCTURE, TABLETS} from './NodePages';
26+
import NodeStructure from './NodeStructure/NodeStructure';
2227

2328
import './Node.scss';
2429

@@ -50,11 +55,16 @@ export function Node(props: NodeProps) {
5055
);
5156
const loading = isFetching && currentData === undefined;
5257
const node = currentData;
58+
const capabilitiesLoaded = useCapabilitiesLoaded();
59+
const isDiskPagesAvailable = useDiskPagesAvailable();
5360

5461
const {activeTabVerified, nodeTabs} = React.useMemo(() => {
5562
const hasStorage = node?.Roles?.find((el) => el === STORAGE_ROLE);
5663

57-
const nodePages = hasStorage ? NODE_PAGES : NODE_PAGES.filter((el) => el.id !== STORAGE);
64+
let nodePages = hasStorage ? NODE_PAGES : NODE_PAGES.filter((el) => el.id !== STORAGE);
65+
if (isDiskPagesAvailable) {
66+
nodePages = nodePages.filter((el) => el.id !== STRUCTURE);
67+
}
5868

5969
const actualNodeTabs = nodePages.map((page) => {
6070
return {
@@ -69,7 +79,7 @@ export function Node(props: NodeProps) {
6979
}
7080

7181
return {activeTabVerified: actualActiveTab, nodeTabs: actualNodeTabs};
72-
}, [activeTab, node]);
82+
}, [activeTab, node, isDiskPagesAvailable]);
7383

7484
const tenantName = node?.Tenants?.[0] || tenantNameFromQuery?.toString();
7585

@@ -131,6 +141,9 @@ export function Node(props: NodeProps) {
131141
);
132142
}
133143

144+
case STRUCTURE: {
145+
return <NodeStructure className={b('node-page-wrapper')} nodeId={nodeId} />;
146+
}
134147
case OVERVIEW: {
135148
return <FullNodeViewer node={node} className={b('overview-wrapper')} />;
136149
}
@@ -140,7 +153,7 @@ export function Node(props: NodeProps) {
140153
}
141154
};
142155

143-
if (loading) {
156+
if (loading || !capabilitiesLoaded) {
144157
return <Loader size="l" />;
145158
}
146159

src/containers/Node/NodePages.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@ export const NODE_PAGES = [
1717
id: STORAGE,
1818
name: 'Storage',
1919
},
20-
// TODO: remove Node Structure component
21-
// {
22-
// id: STRUCTURE,
23-
// name: 'Structure',
24-
// },
20+
{
21+
id: STRUCTURE,
22+
name: 'Structure',
23+
},
2524
{
2625
id: TABLETS,
2726
name: 'Tablets',

src/containers/PDiskPage/PDiskPage.tsx

+14-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {PageMetaWithAutorefresh} from '../../components/PageMeta/PageMeta';
1717
import {getPDiskPagePath} from '../../routes';
1818
import {api} from '../../store/reducers/api';
1919
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
20+
import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks';
2021
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
2122
import {pDiskApi} from '../../store/reducers/pdisk/pdisk';
2223
import type {EDecommitStatus} from '../../types/api/pdisk';
@@ -61,6 +62,7 @@ export function PDiskPage() {
6162
const dispatch = useTypedDispatch();
6263

6364
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
65+
const newDiskApiAvailable = useDiskPagesAvailable();
6466

6567
const [{nodeId, pDiskId, activeTab}] = useQueryParams({
6668
activeTab: StringParam,
@@ -87,7 +89,9 @@ export function PDiskPage() {
8789

8890
const handleRestart = async (isRetry?: boolean) => {
8991
if (pDiskParamsDefined) {
90-
const response = await window.api.restartPDisk({nodeId, pDiskId, force: isRetry});
92+
const response = await window.api[
93+
newDiskApiAvailable ? 'restartPDisk' : 'restartPDiskOld'
94+
]({nodeId, pDiskId, force: isRetry});
9195

9296
if (response?.result === false) {
9397
const err = {
@@ -188,13 +192,15 @@ export function PDiskPage() {
188192
<Icon data={ArrowRotateLeft} />
189193
{pDiskPageKeyset('restart-pdisk-button')}
190194
</ButtonWithConfirmDialog>
191-
<DecommissionButton
192-
decommission={DecommitStatus}
193-
onConfirmAction={handleDecommissionChange}
194-
onConfirmActionSuccess={handleAfterAction}
195-
buttonDisabled={!pDiskParamsDefined || !isUserAllowedToMakeChanges}
196-
popoverDisabled={isUserAllowedToMakeChanges}
197-
/>
195+
{newDiskApiAvailable ? (
196+
<DecommissionButton
197+
decommission={DecommitStatus}
198+
onConfirmAction={handleDecommissionChange}
199+
onConfirmActionSuccess={handleAfterAction}
200+
buttonDisabled={!pDiskParamsDefined || !isUserAllowedToMakeChanges}
201+
popoverDisabled={isUserAllowedToMakeChanges}
202+
/>
203+
) : null}
198204
</div>
199205
);
200206
};

src/containers/VDiskPage/VDiskPage.tsx

+18-18
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {PageMetaWithAutorefresh} from '../../components/PageMeta/PageMeta';
1414
import {VDiskInfo} from '../../components/VDiskInfo/VDiskInfo';
1515
import {api} from '../../store/reducers/api';
1616
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
17+
import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks';
1718
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
1819
import {vDiskApi} from '../../store/reducers/vdisk/vdisk';
1920
import {valueIsDefined} from '../../utils';
@@ -33,6 +34,7 @@ export function VDiskPage() {
3334
const dispatch = useTypedDispatch();
3435

3536
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
37+
const newDiskApiAvailable = useDiskPagesAvailable();
3638

3739
const [{nodeId, pDiskId, vDiskSlotId}] = useQueryParams({
3840
nodeId: StringParam,
@@ -68,24 +70,22 @@ export function VDiskPage() {
6870

6971
const handleEvictVDisk = async (isRetry?: boolean) => {
7072
if (vDiskIdParamsDefined) {
71-
return window.api
72-
.evictVDisk({
73-
groupId: GroupID,
74-
groupGeneration: GroupGeneration,
75-
failRealmIdx: Ring,
76-
failDomainIdx: Domain,
77-
vDiskIdx: VDisk,
78-
force: isRetry,
79-
})
80-
.then((response) => {
81-
if (response?.result === false) {
82-
const err = {
83-
statusText: response.error,
84-
retryPossible: response.forceRetryPossible,
85-
};
86-
throw err;
87-
}
88-
});
73+
return window.api[newDiskApiAvailable ? 'evictVDisk' : 'evictVDiskOld']({
74+
groupId: GroupID,
75+
groupGeneration: GroupGeneration,
76+
failRealmIdx: Ring,
77+
failDomainIdx: Domain,
78+
vDiskIdx: VDisk,
79+
force: isRetry,
80+
}).then((response) => {
81+
if (response?.result === false) {
82+
const err = {
83+
statusText: response.error,
84+
retryPossible: response.forceRetryPossible,
85+
};
86+
throw err;
87+
}
88+
});
8989
}
9090

9191
return undefined;

src/services/api.ts

+60
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
DEV_ENABLE_TRACING_FOR_ALL_REQUESTS,
5959
SECOND_IN_MS,
6060
} from '../utils/constants';
61+
import {createPDiskDeveloperUILink} from '../utils/developerUI/developerUI';
6162
import {isAxiosError} from '../utils/response';
6263
import type {Nullable} from '../utils/typecheckers';
6364

@@ -629,6 +630,45 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
629630
{concurrentId, requestConfig: {signal}},
630631
);
631632
}
633+
evictVDiskOld({
634+
groupId,
635+
groupGeneration,
636+
failRealmIdx,
637+
failDomainIdx,
638+
vDiskIdx,
639+
}: {
640+
groupId: string | number;
641+
groupGeneration: string | number;
642+
failRealmIdx: string | number;
643+
failDomainIdx: string | number;
644+
vDiskIdx: string | number;
645+
}) {
646+
// BSC Id is constant for all ydb clusters
647+
const BSC_TABLET_ID = '72057594037932033';
648+
649+
return this.post(
650+
this.getPath(`/tablets/app?TabletID=${BSC_TABLET_ID}&exec=1`),
651+
{
652+
Command: {
653+
ReassignGroupDisk: {
654+
GroupId: groupId,
655+
GroupGeneration: groupGeneration,
656+
FailRealmIdx: failRealmIdx,
657+
FailDomainIdx: failDomainIdx,
658+
VDiskIdx: vDiskIdx,
659+
},
660+
},
661+
},
662+
{},
663+
{
664+
headers: {
665+
// This handler requires exactly this string
666+
// Automatic headers may not suit
667+
Accept: 'application/json',
668+
},
669+
},
670+
);
671+
}
632672
evictVDisk({
633673
groupId,
634674
groupGeneration,
@@ -661,6 +701,26 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
661701
},
662702
);
663703
}
704+
705+
restartPDiskOld({nodeId, pDiskId}: {nodeId: number | string; pDiskId: number | string}) {
706+
const pDiskPath = createPDiskDeveloperUILink({
707+
nodeId,
708+
pDiskId,
709+
host: this.getPath(''),
710+
});
711+
712+
return this.post<ModifyDiskResponse>(
713+
pDiskPath,
714+
'restartPDisk=',
715+
{},
716+
{
717+
headers: {
718+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
719+
},
720+
},
721+
);
722+
}
723+
664724
restartPDisk({
665725
nodeId,
666726
pDiskId,

src/store/reducers/capabilities/capabilities.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {createSelector} from '@reduxjs/toolkit';
22

33
import type {Capability} from '../../../types/api/capabilities';
4-
import type {RootState} from '../../defaultStore';
4+
import type {AppDispatch, RootState} from '../../defaultStore';
55

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

@@ -23,10 +23,21 @@ export const capabilitiesApi = api.injectEndpoints({
2323
overrideExisting: 'throw',
2424
});
2525

26-
const selectCapabilities = capabilitiesApi.endpoints.getClusterCapabilities.select(undefined);
26+
export const selectCapabilities =
27+
capabilitiesApi.endpoints.getClusterCapabilities.select(undefined);
2728

2829
export const selectCapabilityVersion = createSelector(
2930
(state: RootState) => state,
3031
(_state: RootState, capability: Capability) => capability,
3132
(state, capability) => selectCapabilities(state).data?.Capabilities?.[capability],
3233
);
34+
35+
export async function queryCapability(
36+
capability: Capability,
37+
{dispatch, getState}: {dispatch: AppDispatch; getState: () => RootState},
38+
) {
39+
const thunk = capabilitiesApi.util.getRunningQueryThunk('getClusterCapabilities', undefined);
40+
await dispatch(thunk);
41+
42+
return selectCapabilityVersion(getState(), capability) || 0;
43+
}

src/store/reducers/pdisk/pdisk.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import type {TPDiskInfoResponse} from '../../../types/api/pdisk';
12
import {getPDiskId} from '../../../utils/disks/helpers';
3+
import type {GetState} from '../../defaultStore';
24
import {api} from '../api';
5+
import {queryCapability} from '../capabilities/capabilities';
36

47
import {preparePDiskDataResponse} from './utils';
58

@@ -11,10 +14,37 @@ interface PDiskParams {
1114
export const pDiskApi = api.injectEndpoints({
1215
endpoints: (build) => ({
1316
getPdiskInfo: build.query({
14-
queryFn: async ({nodeId, pDiskId}: PDiskParams, {signal}) => {
17+
queryFn: async ({nodeId, pDiskId}: PDiskParams, {signal, getState, dispatch}) => {
18+
const pDiskInfoHandlerVersion = await queryCapability('/pdisk/info', {
19+
getState: getState as GetState,
20+
dispatch,
21+
});
22+
const newApiAvailable = pDiskInfoHandlerVersion > 0;
23+
24+
let diskInfoPromise: Promise<TPDiskInfoResponse>;
25+
if (newApiAvailable) {
26+
diskInfoPromise = window.api.getPDiskInfo({nodeId, pDiskId}, {signal});
27+
} else {
28+
diskInfoPromise = window.api
29+
.getNodeWhiteboardPDiskInfo({nodeId, pDiskId}, {signal})
30+
.then((result) => {
31+
if (result.PDiskStateInfo) {
32+
return {
33+
Whiteboard: {
34+
PDisk: {
35+
...result.PDiskStateInfo[0],
36+
ExpectedSlotCount: undefined,
37+
},
38+
},
39+
};
40+
}
41+
return {};
42+
});
43+
}
44+
1545
try {
1646
const response = await Promise.all([
17-
window.api.getPDiskInfo({nodeId, pDiskId}, {signal}),
47+
diskInfoPromise,
1848
window.api.getNodeInfo(nodeId, {signal}),
1949
]);
2050
const data = preparePDiskDataResponse(response);

0 commit comments

Comments
 (0)