ydb-embedded-ui 1.0.4 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/assets/icons/question.svg +1 -0
  3. package/dist/components/AsideNavigation/AsideHeader.tsx +2 -1
  4. package/dist/components/ClusterInfo/ClusterInfo.tsx +8 -4
  5. package/dist/components/FullNodeViewer/FullNodeViewer.scss +4 -9
  6. package/dist/components/InfoViewer/InfoViewer.scss +3 -2
  7. package/dist/components/InternalLink/InternalLink.js +8 -0
  8. package/dist/components/Loader/Loader.scss +5 -0
  9. package/dist/components/Loader/Loader.tsx +16 -0
  10. package/dist/components/PDiskViewer/PDiskViewer.js +3 -4
  11. package/dist/containers/App/App.scss +4 -0
  12. package/dist/containers/App/Content.js +0 -2
  13. package/dist/containers/AppIcons/AppIcons.js +4 -0
  14. package/dist/containers/Authentication/Authentication.tsx +2 -2
  15. package/dist/containers/Cluster/Cluster.tsx +1 -1
  16. package/dist/containers/Header/Header.tsx +6 -1
  17. package/dist/containers/Heatmap/Heatmap.js +0 -1
  18. package/dist/containers/Node/Node.scss +12 -1
  19. package/dist/containers/Node/Node.tsx +174 -0
  20. package/dist/containers/Node/NodeOverview/NodeOverview.scss +0 -0
  21. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +23 -0
  22. package/dist/containers/Node/NodePages.js +16 -0
  23. package/dist/containers/Node/NodeStructure/NodeStructure.scss +151 -0
  24. package/dist/containers/Node/NodeStructure/NodeStructure.tsx +155 -0
  25. package/dist/containers/Node/NodeStructure/Pdisk.tsx +299 -0
  26. package/dist/containers/Node/NodeStructure/Vdisk.tsx +153 -0
  27. package/dist/containers/Pdisk/Pdisk.js +2 -5
  28. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +10 -3
  29. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +20 -15
  30. package/dist/containers/Storage/Pdisk/Pdisk.scss +1 -0
  31. package/dist/containers/Storage/Pdisk/Pdisk.tsx +7 -5
  32. package/dist/containers/Storage/Storage.js +12 -9
  33. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +2 -1
  34. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +2 -2
  35. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +3 -2
  36. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +2 -2
  37. package/dist/containers/Storage/Vdisk/Vdisk.js +7 -6
  38. package/dist/containers/Storage/Vdisk/Vdisk.scss +1 -0
  39. package/dist/containers/Tablet/Tablet.js +2 -7
  40. package/dist/containers/Tablets/Tablets.js +4 -12
  41. package/dist/containers/Tenant/Acl/Acl.js +0 -3
  42. package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +1 -3
  43. package/dist/containers/Tenant/Diagnostics/Network/Network.js +3 -6
  44. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +2 -2
  45. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +24 -24
  46. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +4 -0
  47. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +4 -1
  48. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +1 -0
  49. package/dist/containers/Vdisk/Vdisk.js +2 -4
  50. package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +4 -6
  51. package/dist/services/api.js +0 -1
  52. package/dist/store/reducers/executeQuery.js +1 -1
  53. package/dist/store/reducers/header.ts +1 -1
  54. package/dist/store/reducers/node.js +98 -3
  55. package/dist/store/reducers/nodes.js +0 -3
  56. package/dist/store/reducers/storage.js +8 -2
  57. package/dist/store/reducers/tablets.js +0 -3
  58. package/dist/utils/constants.js +0 -6
  59. package/dist/utils/getNodesColumns.js +2 -9
  60. package/dist/utils/utils.js +10 -1
  61. package/package.json +43 -29
  62. package/dist/containers/Node/Node.js +0 -184
@@ -0,0 +1,151 @@
1
+ @import '../../../styles/mixins.scss';
2
+
3
+ .kv-node-structure {
4
+ position: relative;
5
+
6
+ display: flex;
7
+ overflow: auto;
8
+ flex-direction: column;
9
+ flex-shrink: 0;
10
+ @include flex-container();
11
+ @include body2-typography();
12
+
13
+ &__loader {
14
+ display: flex;
15
+ flex-grow: 1;
16
+ justify-content: center;
17
+ }
18
+
19
+ &__pdisk {
20
+ display: flex;
21
+ flex-direction: column;
22
+
23
+ width: 573px;
24
+ margin-bottom: 8px;
25
+ padding-left: 20px;
26
+
27
+ border: 1px solid var(--yc-color-line-generic);
28
+ border-radius: 5px;
29
+ }
30
+
31
+ &__pdisk-id {
32
+ display: flex;
33
+ align-items: flex-end;
34
+ }
35
+
36
+ &__pdisk-header {
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+
41
+ height: 48px;
42
+ }
43
+
44
+ &__pdisk-title-wrapper {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 8px;
48
+
49
+ font-weight: 600;
50
+ }
51
+
52
+ &__pdisk-details {
53
+ margin-bottom: 20px;
54
+ }
55
+
56
+ &__link {
57
+ text-decoration: none;
58
+
59
+ color: var(--yc-color-base-special);
60
+ }
61
+
62
+ &__vdisks-header {
63
+ font-weight: 600;
64
+ }
65
+
66
+ &__vdisks-container {
67
+ margin-bottom: 42px;
68
+ }
69
+
70
+ &__vdisk-details {
71
+ overflow: auto;
72
+
73
+ min-width: 200px;
74
+ max-height: 90vh;
75
+
76
+ .vdisk-pdisk-node__column {
77
+ margin-bottom: 0;
78
+ }
79
+ .vdisk-pdisk-node__section {
80
+ padding-bottom: 0;
81
+ }
82
+ }
83
+
84
+ &__vdisk-id {
85
+ display: flex;
86
+ align-items: center;
87
+
88
+ &_selected {
89
+ color: var(--yc-color-text-info);
90
+ }
91
+ }
92
+
93
+ &__vdisk-details-button {
94
+ &_selected {
95
+ color: var(--yc-color-text-info);
96
+ }
97
+ }
98
+
99
+ &__external-button {
100
+ display: inline-flex;
101
+ align-items: center;
102
+
103
+ margin-left: 4px;
104
+
105
+ transform: translateY(-1px);
106
+
107
+ .yc-button__text {
108
+ margin: 0 4px;
109
+ }
110
+
111
+ &_hidden {
112
+ visibility: hidden;
113
+ }
114
+ }
115
+
116
+ .data-table__row:hover {
117
+ .kv-node-structure__external-button_hidden {
118
+ visibility: visible;
119
+ }
120
+ }
121
+
122
+ &__selected-vdisk {
123
+ animation: onSelectedVdiskAnimation 4s;
124
+ }
125
+
126
+ &__row {
127
+ display: flex;
128
+ }
129
+
130
+ &__column {
131
+ display: flex;
132
+ flex-direction: column;
133
+
134
+ margin-bottom: 15px;
135
+ }
136
+
137
+ &__title {
138
+ margin-right: 16px;
139
+
140
+ font-size: var(--yc-text-body2-font-size);
141
+ font-weight: 500;
142
+ line-height: var(--yc-text-body2-line-height);
143
+ text-transform: uppercase;
144
+ }
145
+ }
146
+
147
+ @keyframes onSelectedVdiskAnimation {
148
+ 0% {
149
+ background-color: var(--yc-color-base-info-hover);
150
+ }
151
+ }
@@ -0,0 +1,155 @@
1
+ import {useEffect, useRef, useMemo} from 'react';
2
+ import {useDispatch, useSelector} from 'react-redux';
3
+ import url from 'url';
4
+ import _ from 'lodash';
5
+
6
+ import cn from 'bem-cn-lite';
7
+
8
+ import {PDisk} from './Pdisk';
9
+ import Loader from '../.././../components/Loader/Loader';
10
+
11
+ import {getNodeStructure, selectNodeStructure} from '../../../store/reducers/node';
12
+
13
+ import {AutoFetcher} from '../../../utils/autofetcher';
14
+
15
+ import './NodeStructure.scss';
16
+
17
+ const b = cn('kv-node-structure');
18
+
19
+ export function valueIsDefined(value: any) {
20
+ return value !== null && value !== undefined;
21
+ }
22
+
23
+ function generateId({type, id}: {type: 'pdisk' | 'vdisk'; id: string}) {
24
+ return `${type}-${id}`;
25
+ }
26
+
27
+ interface NodeStructureProps {
28
+ nodeId: string;
29
+ className?: string;
30
+ additionalNodesInfo?: any;
31
+ }
32
+
33
+ const autofetcher = new AutoFetcher();
34
+
35
+ function NodeStructure(props: NodeStructureProps) {
36
+ const dispatch = useDispatch();
37
+
38
+ const nodeStructure: any = useSelector(selectNodeStructure);
39
+
40
+ const loadingStructure = useSelector((state: any) => state.node.loadingStructure);
41
+ const wasLoadedStructure = useSelector((state: any) => state.node.wasLoadedStructure);
42
+ const nodeData = useSelector((state: any) => state.node?.data?.SystemStateInfo?.[0]);
43
+
44
+ const nodeHref = useMemo(() => {
45
+ return props.additionalNodesInfo?.getNodeRef
46
+ ? props.additionalNodesInfo.getNodeRef(nodeData)
47
+ : undefined;
48
+ }, [nodeData, props.additionalNodesInfo]);
49
+
50
+ const {pdiskId: pdiskIdFromUrl, vdiskId: vdiskIdFromUrl} = url.parse(
51
+ window.location.href,
52
+ true,
53
+ ).query;
54
+
55
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
56
+ const scrollContainer = scrollContainerRef.current;
57
+
58
+ const isReady = useRef(false);
59
+
60
+ const scrolled = useRef(false);
61
+
62
+ useEffect(() => {
63
+ return () => {
64
+ if (scrollContainer) {
65
+ scrollContainer.scrollTo({
66
+ behavior: 'smooth',
67
+ top: 0,
68
+ });
69
+ }
70
+ };
71
+ }, []);
72
+
73
+ useEffect(() => {
74
+ dispatch(getNodeStructure(props.nodeId));
75
+ autofetcher.start();
76
+ autofetcher.fetch(() => dispatch(getNodeStructure(props.nodeId)));
77
+
78
+ return () => {
79
+ scrolled.current = false;
80
+ isReady.current = false;
81
+ autofetcher.stop();
82
+ };
83
+ }, [props.nodeId, dispatch]);
84
+
85
+ useEffect(() => {
86
+ if (!_.isEmpty(nodeStructure) && scrollContainer) {
87
+ isReady.current = true;
88
+ }
89
+ }, [nodeStructure]);
90
+
91
+ useEffect(() => {
92
+ if (isReady.current && !scrolled.current && scrollContainer) {
93
+ const element = document.getElementById(
94
+ generateId({type: 'pdisk', id: pdiskIdFromUrl as string}),
95
+ );
96
+
97
+ let scrollToVdisk = 0;
98
+
99
+ if (vdiskIdFromUrl) {
100
+ const vDisks = nodeStructure[pdiskIdFromUrl as string]?.vDisks;
101
+ const vDisk = vDisks?.find((el: any) => el.id === vdiskIdFromUrl);
102
+ const dataTable = vDisk ? document.querySelector('.data-table') : undefined;
103
+ const order = vDisk?.order;
104
+
105
+ if (dataTable) {
106
+ scrollToVdisk += (dataTable as HTMLElement).offsetTop + 40 * order;
107
+ }
108
+ }
109
+
110
+ if (element) {
111
+ scrollContainer.scrollTo({
112
+ behavior: 'smooth',
113
+ // should subtract 20 to avoid sticking the element to tabs
114
+ top: scrollToVdisk ? scrollToVdisk : element.offsetTop,
115
+ });
116
+ scrolled.current = true;
117
+ }
118
+ }
119
+ }, [nodeStructure, pdiskIdFromUrl, vdiskIdFromUrl]);
120
+
121
+ const renderStub = () => {
122
+ return 'There is no information about node structure.';
123
+ };
124
+
125
+ const renderStructure = () => {
126
+ const pDisksIds = Object.keys(nodeStructure);
127
+ return pDisksIds.length > 0
128
+ ? pDisksIds.map((pDiskId) => (
129
+ <PDisk
130
+ data={nodeStructure[pDiskId]}
131
+ key={pDiskId}
132
+ id={generateId({type: 'pdisk', id: pDiskId})}
133
+ unfolded={pdiskIdFromUrl === pDiskId}
134
+ selectedVdiskId={vdiskIdFromUrl as string}
135
+ nodeHref={nodeHref}
136
+ />
137
+ ))
138
+ : renderStub();
139
+ };
140
+
141
+ const renderContent = () => {
142
+ if (loadingStructure && !wasLoadedStructure) {
143
+ return <Loader size="m" />;
144
+ }
145
+ return renderStructure();
146
+ };
147
+
148
+ return (
149
+ <div className={b()} ref={scrollContainerRef}>
150
+ <div className={props.className}>{renderContent()}</div>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ export default NodeStructure;
@@ -0,0 +1,299 @@
1
+ import {useState} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+ import _ from 'lodash';
4
+
5
+ import {ArrowToggle, Button, Tooltip} from '@yandex-cloud/uikit';
6
+
7
+ import DataTable, {Column, Settings} from '@yandex-cloud/react-data-table';
8
+
9
+ import EntityStatus from '../../../components/EntityStatus/EntityStatus';
10
+ import InfoViewer from '../../../components/InfoViewer/InfoViewer';
11
+ import ProgressViewer from '../../../components/ProgressViewer/ProgressViewer';
12
+ import Icon from '../../../components/Icon/Icon';
13
+ import {Vdisk} from './Vdisk';
14
+
15
+ import {bytesToGB, pad9} from '../../../utils/utils';
16
+ import {formatStorageValuesToGb} from '../../../utils';
17
+
18
+ import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
19
+ import {valueIsDefined} from './NodeStructure';
20
+
21
+ const b = cn('kv-node-structure');
22
+
23
+ interface PDiskProps {
24
+ data: Record<string, any>;
25
+ unfolded?: boolean;
26
+ id: string;
27
+ selectedVdiskId?: string;
28
+ nodeHref?: string;
29
+ }
30
+
31
+ enum VDiskTableColumnsIds {
32
+ slotId = 'VDiskSlotId',
33
+ VDiskState = 'VDiskState',
34
+ Size = 'Size',
35
+ Info = 'Info',
36
+ }
37
+
38
+ type VDiskTableColumnsIdsKeys = keyof typeof VDiskTableColumnsIds;
39
+ type VDiskTableColumnsIdsValues = typeof VDiskTableColumnsIds[VDiskTableColumnsIdsKeys];
40
+
41
+ const vDiskTableColumnsNames: Record<VDiskTableColumnsIdsValues, string> = {
42
+ VDiskSlotId: 'Slot id',
43
+ VDiskState: 'Status',
44
+ Size: 'Size',
45
+ Info: '',
46
+ };
47
+
48
+ interface RowType {
49
+ id: string;
50
+ [VDiskTableColumnsIds.slotId]: number;
51
+ [VDiskTableColumnsIds.VDiskState]: string;
52
+ AllocatedSize: string;
53
+ AvailableSize: string;
54
+ }
55
+
56
+ function getColumns({
57
+ pDiskId,
58
+ selectedVdiskId,
59
+ nodeHref,
60
+ }: {
61
+ pDiskId: number;
62
+ selectedVdiskId?: string;
63
+ nodeHref?: string;
64
+ }) {
65
+ const columns: Column<RowType>[] = [
66
+ {
67
+ name: VDiskTableColumnsIds.slotId as string,
68
+ header: vDiskTableColumnsNames[VDiskTableColumnsIds.slotId],
69
+ width: 100,
70
+ render: ({value, row}) => {
71
+ let vdiskInternalViewerLink: string | undefined;
72
+
73
+ if (nodeHref && value !== undefined) {
74
+ vdiskInternalViewerLink +=
75
+ nodeHref + '/actors/vdisks/vdisk' + pad9(pDiskId) + '_' + pad9(value);
76
+ }
77
+
78
+ return (
79
+ <div className={b('vdisk-id', {selected: row.id === selectedVdiskId})}>
80
+ <span>{value as number}</span>
81
+ {vdiskInternalViewerLink && (
82
+ <Button
83
+ size="s"
84
+ className={b('external-button', {hidden: true})}
85
+ href={vdiskInternalViewerLink}
86
+ target="_blank"
87
+ >
88
+ <Icon name="external" />
89
+ </Button>
90
+ )}
91
+ </div>
92
+ );
93
+ },
94
+ align: DataTable.LEFT,
95
+ },
96
+ {
97
+ name: VDiskTableColumnsIds.VDiskState as string,
98
+ header: vDiskTableColumnsNames[VDiskTableColumnsIds.VDiskState],
99
+ width: 70,
100
+ render: ({value}) => {
101
+ return <EntityStatus status={value === 'OK' ? 'green' : 'red'} />;
102
+ },
103
+ sortAccessor: (row) => (row[VDiskTableColumnsIds.VDiskState] === 'OK' ? 1 : 0),
104
+ align: DataTable.CENTER,
105
+ },
106
+ {
107
+ name: VDiskTableColumnsIds.Size as string,
108
+ header: vDiskTableColumnsNames[VDiskTableColumnsIds.Size],
109
+ width: 100,
110
+ render: ({row}) => {
111
+ return (
112
+ <ProgressViewer
113
+ value={row.AllocatedSize}
114
+ capacity={Number(row.AllocatedSize) + Number(row.AvailableSize)}
115
+ formatValues={formatStorageValuesToGb}
116
+ colorizeProgress={true}
117
+ />
118
+ );
119
+ },
120
+ sortAccessor: (row) => Number(row.AllocatedSize),
121
+ align: DataTable.CENTER,
122
+ },
123
+ {
124
+ name: VDiskTableColumnsIds.Info as string,
125
+ header: vDiskTableColumnsNames[VDiskTableColumnsIds.Info],
126
+ width: 70,
127
+ render: ({row}) => {
128
+ return (
129
+ <Tooltip
130
+ placement={['right']}
131
+ content={<Vdisk {...row} />}
132
+ contentClassName={b('vdisk-details')}
133
+ >
134
+ <Button
135
+ view="clear"
136
+ className={b('vdisk-details-button', {
137
+ selected: row.id === selectedVdiskId,
138
+ })}
139
+ >
140
+ <Icon name="information" viewBox="0 0 512 512" height={16} width={16} />
141
+ </Button>
142
+ </Tooltip>
143
+ );
144
+ },
145
+ sortable: false,
146
+ },
147
+ ];
148
+ return columns;
149
+ }
150
+
151
+ export function PDisk(props: PDiskProps) {
152
+ const [unfolded, setUnfolded] = useState(props.unfolded ?? false);
153
+
154
+ const data = props.data ?? {};
155
+
156
+ const onOpenPDiskDetails = () => {
157
+ setUnfolded(true);
158
+ };
159
+ const onClosePDiskDetails = () => {
160
+ setUnfolded(false);
161
+ };
162
+
163
+ const renderVDisks = () => {
164
+ const {selectedVdiskId, data, nodeHref} = props;
165
+ const {vDisks} = data;
166
+
167
+ return (
168
+ <DataTable
169
+ theme="yandex-cloud"
170
+ data={vDisks}
171
+ columns={getColumns({nodeHref, pDiskId: data.PDiskId, selectedVdiskId})}
172
+ settings={{...DEFAULT_TABLE_SETTINGS, dynamicRender: false} as Settings}
173
+ rowClassName={(row) => {
174
+ return row.id === selectedVdiskId ? b('selected-vdisk') : '';
175
+ }}
176
+ />
177
+ );
178
+ };
179
+
180
+ const renderPDiskDetails = () => {
181
+ if (_.isEmpty(data)) {
182
+ return <div>No information about PDisk</div>;
183
+ }
184
+ const {nodeHref} = props;
185
+ const {
186
+ TotalSize,
187
+ AvailableSize,
188
+ Device,
189
+ Guid,
190
+ PDiskId,
191
+ Path,
192
+ Realtime,
193
+ State,
194
+ Category,
195
+ SerialNumber,
196
+ } = data;
197
+
198
+ let pDiskInternalViewerLink: string | undefined;
199
+
200
+ if (nodeHref) {
201
+ pDiskInternalViewerLink += nodeHref + '/actors/pdisks/pdisk' + pad9(PDiskId);
202
+ }
203
+
204
+ const pdiskInfo: any = [
205
+ {
206
+ label: 'PDisk Id',
207
+ value: (
208
+ <div className={b('pdisk-id')}>
209
+ {PDiskId}
210
+ {pDiskInternalViewerLink && (
211
+ <Button
212
+ size="s"
213
+ className={b('external-button')}
214
+ href={pDiskInternalViewerLink}
215
+ target="_blank"
216
+ view="clear"
217
+ >
218
+ <Icon name="external" />
219
+ </Button>
220
+ )}
221
+ </div>
222
+ ),
223
+ },
224
+ ];
225
+ if (valueIsDefined(Path)) {
226
+ pdiskInfo.push({label: 'Path', value: Path});
227
+ }
228
+ if (valueIsDefined(Guid)) {
229
+ pdiskInfo.push({label: 'GUID', value: Guid});
230
+ }
231
+ if (valueIsDefined(Category)) {
232
+ pdiskInfo.push({label: 'Category', value: Category});
233
+ }
234
+ pdiskInfo.push({
235
+ label: 'Allocated Size',
236
+ value: bytesToGB(TotalSize - AvailableSize),
237
+ });
238
+ pdiskInfo.push({
239
+ label: 'Available Size',
240
+ value: bytesToGB(AvailableSize),
241
+ });
242
+ if (Number(TotalSize) >= 0 && Number(AvailableSize) >= 0) {
243
+ pdiskInfo.push({
244
+ label: 'Size',
245
+ value: (
246
+ <ProgressViewer
247
+ value={TotalSize - AvailableSize}
248
+ capacity={TotalSize}
249
+ formatValues={formatStorageValuesToGb}
250
+ colorizeProgress={true}
251
+ className={b('size')}
252
+ />
253
+ ),
254
+ });
255
+ }
256
+ if (valueIsDefined(State)) {
257
+ pdiskInfo.push({label: 'State', value: State});
258
+ }
259
+ if (valueIsDefined(Device)) {
260
+ pdiskInfo.push({
261
+ label: 'Device',
262
+ value: <EntityStatus status={Device} />,
263
+ });
264
+ }
265
+ if (valueIsDefined(Realtime)) {
266
+ pdiskInfo.push({
267
+ label: 'Realtime',
268
+ value: <EntityStatus status={Realtime} />,
269
+ });
270
+ }
271
+ if (valueIsDefined(SerialNumber)) {
272
+ pdiskInfo.push({label: 'SerialNumber', value: SerialNumber});
273
+ }
274
+ return (
275
+ <div>
276
+ <InfoViewer className={b('pdisk-details')} info={pdiskInfo} />
277
+ <div className={b('vdisks-container')}>
278
+ <div className={b('vdisks-header')}>VDisks</div>
279
+ {renderVDisks()}
280
+ </div>
281
+ </div>
282
+ );
283
+ };
284
+
285
+ return (
286
+ <div className={b('pdisk')} id={props.id}>
287
+ <div className={b('pdisk-header')}>
288
+ <div className={b('pdisk-title-wrapper')}>
289
+ <span>{data.Path}</span>
290
+ <EntityStatus status={data.Device} name={`${data.NodeId}-${data.PDiskId}`} />
291
+ </div>
292
+ <Button onClick={unfolded ? onClosePDiskDetails : onOpenPDiskDetails} view="clear">
293
+ <ArrowToggle direction={unfolded ? 'top' : 'bottom'} />
294
+ </Button>
295
+ </div>
296
+ {unfolded && renderPDiskDetails()}
297
+ </div>
298
+ );
299
+ }