ydb-embedded-ui 22.1.0 → 22.2.0

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 (116) hide show
  1. package/dist/components/DiskStateProgressBar/AllModeIndicators.js +4 -1
  2. package/dist/components/DiskStateProgressBar/AllModeIndicators.js.map +1 -1
  3. package/dist/components/DiskStateProgressBar/DiskIndicator.js +5 -1
  4. package/dist/components/DiskStateProgressBar/DiskIndicator.js.map +1 -1
  5. package/dist/components/DiskStateProgressBar/DiskStateProgressBar.scss +50 -8
  6. package/dist/components/PDiskInfo/PDiskInfo.js +2 -1
  7. package/dist/components/PDiskInfo/PDiskInfo.js.map +1 -1
  8. package/dist/components/PDiskPopup/PDiskPopup.js +5 -1
  9. package/dist/components/PDiskPopup/PDiskPopup.js.map +1 -1
  10. package/dist/components/PaginatedTable/PaginatedTable.js +27 -15
  11. package/dist/components/PaginatedTable/PaginatedTable.js.map +1 -1
  12. package/dist/components/PaginatedTable/PaginatedTableContext.d.ts +2 -3
  13. package/dist/components/PaginatedTable/PaginatedTableContext.js +10 -11
  14. package/dist/components/PaginatedTable/PaginatedTableContext.js.map +1 -1
  15. package/dist/components/PaginatedTable/TableChunk.d.ts +1 -1
  16. package/dist/components/PaginatedTable/TableChunk.js +2 -1
  17. package/dist/components/PaginatedTable/TableChunk.js.map +1 -1
  18. package/dist/components/PaginatedTable/TableChunksRenderer.d.ts +1 -1
  19. package/dist/components/PaginatedTable/tableCounts.d.ts +19 -0
  20. package/dist/components/PaginatedTable/tableCounts.js +30 -0
  21. package/dist/components/PaginatedTable/tableCounts.js.map +1 -0
  22. package/dist/components/VDisk/VDisk.d.ts +2 -1
  23. package/dist/components/VDisk/VDisk.js +17 -7
  24. package/dist/components/VDisk/VDisk.js.map +1 -1
  25. package/dist/components/VDisk/VDiskWithDonorsStack.d.ts +3 -2
  26. package/dist/components/VDisk/VDiskWithDonorsStack.js +2 -1
  27. package/dist/components/VDisk/VDiskWithDonorsStack.js.map +1 -1
  28. package/dist/components/VDisk/i18n/en.json +3 -1
  29. package/dist/components/VDisk/i18n/index.d.ts +1 -1
  30. package/dist/containers/Storage/Disks/Disks.js +1 -3
  31. package/dist/containers/Storage/Disks/Disks.js.map +1 -1
  32. package/dist/containers/Storage/PDisk/PDisk.js +9 -2
  33. package/dist/containers/Storage/PDisk/PDisk.js.map +1 -1
  34. package/dist/containers/Storage/PDisk/i18n/en.json +1 -0
  35. package/dist/containers/Storage/PDisk/i18n/index.d.ts +1 -1
  36. package/dist/containers/Storage/StorageExpertModePanel/StorageGroupsExpertModePanel.js +2 -0
  37. package/dist/containers/Storage/StorageExpertModePanel/StorageGroupsExpertModePanel.js.map +1 -1
  38. package/dist/containers/Storage/StorageExpertModePanel/components/DriveTypeLegend.d.ts +1 -0
  39. package/dist/containers/Storage/StorageExpertModePanel/components/DriveTypeLegend.js +7 -0
  40. package/dist/containers/Storage/StorageExpertModePanel/components/DriveTypeLegend.js.map +1 -0
  41. package/dist/containers/Storage/StorageExpertModePanel/components/renderExpertModeLegend.js +5 -0
  42. package/dist/containers/Storage/StorageExpertModePanel/components/renderExpertModeLegend.js.map +1 -1
  43. package/dist/containers/Storage/StorageExpertModePanel/constants.d.ts +3 -0
  44. package/dist/containers/Storage/StorageExpertModePanel/constants.js +1 -0
  45. package/dist/containers/Storage/StorageExpertModePanel/constants.js.map +1 -1
  46. package/dist/containers/Storage/StorageExpertModePanel/i18n/en.json +4 -0
  47. package/dist/containers/Storage/StorageExpertModePanel/i18n/index.d.ts +1 -1
  48. package/dist/containers/Storage/useStoragePDiskDisplayStateGetter.js +7 -0
  49. package/dist/containers/Storage/useStoragePDiskDisplayStateGetter.js.map +1 -1
  50. package/dist/containers/Storage/useStorageQueryParams.d.ts +4 -4
  51. package/dist/containers/Storage/useStorageVDiskDisplayStateGetter.js +10 -1
  52. package/dist/containers/Storage/useStorageVDiskDisplayStateGetter.js.map +1 -1
  53. package/dist/containers/VDiskPage/VDiskPage.js +9 -1
  54. package/dist/containers/VDiskPage/VDiskPage.js.map +1 -1
  55. package/dist/store/reducers/table/table.js +6 -0
  56. package/dist/store/reducers/table/table.js.map +1 -1
  57. package/dist/store/reducers/topic/topic.js +7 -1
  58. package/dist/store/reducers/topic/topic.js.map +1 -1
  59. package/dist/uiFactory/types.d.ts +6 -0
  60. package/dist/utils/disks/displayState.d.ts +3 -2
  61. package/dist/utils/disks/displayState.js.map +1 -1
  62. package/dist/utils/disks/driveType.d.ts +3 -0
  63. package/dist/utils/disks/driveType.js +27 -0
  64. package/dist/utils/disks/driveType.js.map +1 -0
  65. package/dist/utils/disks/getDiskBarTone.d.ts +3 -2
  66. package/dist/utils/disks/getDiskBarTone.js +4 -1
  67. package/dist/utils/disks/getDiskBarTone.js.map +1 -1
  68. package/dist/utils/disks/getPDiskType.d.ts +1 -0
  69. package/dist/utils/disks/getPDiskType.js +3 -0
  70. package/dist/utils/disks/getPDiskType.js.map +1 -1
  71. package/dist/utils/disks/groupBy.d.ts +1 -0
  72. package/dist/utils/disks/groupBy.js +1 -0
  73. package/dist/utils/disks/groupBy.js.map +1 -1
  74. package/dist/utils/disks/iconCalculators.d.ts +1 -1
  75. package/dist/utils/disks/iconCalculators.js +2 -2
  76. package/dist/utils/disks/iconCalculators.js.map +1 -1
  77. package/dist/utils/disks/types.d.ts +1 -1
  78. package/dist/utils/objectCreationMetrics.d.ts +5 -0
  79. package/dist/utils/objectCreationMetrics.js +38 -0
  80. package/dist/utils/objectCreationMetrics.js.map +1 -0
  81. package/package.json +1 -1
  82. package/src/components/DiskStateProgressBar/AllModeIndicators.tsx +12 -1
  83. package/src/components/DiskStateProgressBar/DiskIndicator.tsx +9 -1
  84. package/src/components/DiskStateProgressBar/DiskStateProgressBar.scss +50 -8
  85. package/src/components/PDiskInfo/PDiskInfo.tsx +2 -1
  86. package/src/components/PDiskPopup/PDiskPopup.tsx +5 -1
  87. package/src/components/PaginatedTable/PaginatedTable.tsx +34 -24
  88. package/src/components/PaginatedTable/PaginatedTableContext.tsx +14 -19
  89. package/src/components/PaginatedTable/TableChunk.tsx +3 -2
  90. package/src/components/PaginatedTable/TableChunksRenderer.tsx +1 -1
  91. package/src/components/PaginatedTable/tableCounts.ts +50 -0
  92. package/src/components/VDisk/VDisk.tsx +28 -5
  93. package/src/components/VDisk/VDiskWithDonorsStack.tsx +5 -1
  94. package/src/components/VDisk/i18n/en.json +3 -1
  95. package/src/containers/Storage/Disks/Disks.tsx +2 -4
  96. package/src/containers/Storage/PDisk/PDisk.tsx +11 -2
  97. package/src/containers/Storage/PDisk/i18n/en.json +1 -0
  98. package/src/containers/Storage/StorageExpertModePanel/StorageGroupsExpertModePanel.tsx +2 -0
  99. package/src/containers/Storage/StorageExpertModePanel/components/DriveTypeLegend.tsx +22 -0
  100. package/src/containers/Storage/StorageExpertModePanel/components/renderExpertModeLegend.tsx +5 -0
  101. package/src/containers/Storage/StorageExpertModePanel/constants.ts +1 -0
  102. package/src/containers/Storage/StorageExpertModePanel/i18n/en.json +4 -0
  103. package/src/containers/Storage/useStoragePDiskDisplayStateGetter.ts +8 -0
  104. package/src/containers/Storage/useStorageVDiskDisplayStateGetter.ts +11 -1
  105. package/src/containers/VDiskPage/VDiskPage.tsx +9 -1
  106. package/src/store/reducers/table/table.ts +6 -0
  107. package/src/store/reducers/topic/topic.ts +7 -1
  108. package/src/uiFactory/types.ts +6 -0
  109. package/src/utils/disks/displayState.ts +3 -1
  110. package/src/utils/disks/driveType.ts +30 -0
  111. package/src/utils/disks/getDiskBarTone.ts +7 -1
  112. package/src/utils/disks/getPDiskType.ts +4 -0
  113. package/src/utils/disks/groupBy.ts +1 -0
  114. package/src/utils/disks/iconCalculators.ts +2 -2
  115. package/src/utils/disks/types.ts +1 -1
  116. package/src/utils/objectCreationMetrics.ts +47 -0
@@ -60,7 +60,8 @@
60
60
 
61
61
  border: $border-width solid var(--entity-state-border-color);
62
62
 
63
- &#{$block}_striped {
63
+ &#{$block}_striped,
64
+ &#{$block}_borderless {
64
65
  border: none;
65
66
  }
66
67
 
@@ -133,6 +134,36 @@
133
134
  --entity-state-icon-color: var(--g-color-text-info);
134
135
  }
135
136
 
137
+ &_mode-drivetype {
138
+ @each $type, $theme, $intensity in (ssd misc medium, hdd utility light, nvme info light) {
139
+ &#{$block}_#{$type} {
140
+ --entity-state-background-color: var(--g-color-base-#{$theme}-#{$intensity});
141
+ --ydb-progress-bar-font-color: var(--g-color-text-#{$theme});
142
+
143
+ &#{$block}_highlighted {
144
+ --entity-state-background-color: var(
145
+ --g-color-base-#{$theme}-#{$intensity}-hover
146
+ );
147
+ }
148
+ }
149
+ }
150
+ #{$block}__text-label {
151
+ overflow: hidden;
152
+ flex-shrink: 1;
153
+
154
+ min-width: 0;
155
+ padding-right: var(--g-spacing-1);
156
+
157
+ white-space: nowrap;
158
+
159
+ mask-image: linear-gradient(
160
+ to right,
161
+ #000 calc(100% - var(--g-spacing-1)),
162
+ transparent
163
+ );
164
+ }
165
+ }
166
+
136
167
  &_mode-all {
137
168
  &#{$block}_green {
138
169
  --entity-state-background-color: transparent;
@@ -358,18 +389,19 @@
358
389
 
359
390
  // Keep N/D colors independent from expert mode-specific backgrounds.
360
391
  &_grey {
361
- --ydb-progress-bar-font-color: var(--g-color-text-secondary);
392
+ --ydb-progress-bar-font-color: var(--g-color-text-primary);
393
+ --entity-state-font-color: var(--g-color-text-primary);
362
394
  --entity-state-icon-color: var(--g-color-text-secondary);
363
395
  --entity-state-border-color: none;
364
- --entity-state-background-color: var(--g-color-base-neutral-light);
365
- --entity-state-fill-color: var(--g-color-base-neutral-light);
396
+ --entity-state-background-color: var(--g-color-base-neutral-medium);
397
+ --entity-state-fill-color: var(--g-color-base-neutral-medium);
366
398
 
367
- color: var(--g-color-text-secondary);
399
+ color: var(--g-color-text-primary);
368
400
  border: none;
369
401
 
370
402
  &#{$block}_highlighted {
371
- --entity-state-background-color: var(--g-color-base-neutral-light-hover);
372
- --entity-state-fill-color: var(--g-color-base-neutral-light-hover);
403
+ --entity-state-background-color: var(--g-color-base-neutral-medium-hover);
404
+ --entity-state-fill-color: var(--g-color-base-neutral-medium-hover);
373
405
  }
374
406
  }
375
407
 
@@ -528,7 +560,7 @@
528
560
  }
529
561
 
530
562
  &__all-mode-missing-data-indicator-icon {
531
- color: rgba(162, 162, 162, 1);
563
+ color: var(--g-color-text-hint);
532
564
  }
533
565
 
534
566
  &__all-mode-front-queues-indicator-slot {
@@ -576,6 +608,12 @@
576
608
 
577
609
  color: var(--ydb-progress-bar-font-color);
578
610
 
611
+ &_text,
612
+ &_compact {
613
+ color: var(--g-color-text-primary);
614
+ @include mixins.text-caption-2();
615
+ }
616
+
579
617
  &_text {
580
618
  margin-right: var(--g-spacing-1);
581
619
  }
@@ -621,6 +659,10 @@
621
659
 
622
660
  color: var(--entity-state-icon-color, var(--entity-state-border-color));
623
661
 
662
+ &_missing-data {
663
+ color: var(--g-color-text-hint);
664
+ }
665
+
624
666
  &_overlap-top-left {
625
667
  position: absolute;
626
668
  z-index: 3;
@@ -5,6 +5,7 @@ import {valueIsDefined} from '../../utils';
5
5
  import {formatBytes} from '../../utils/bytesParsers';
6
6
  import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters';
7
7
  import {createPDiskDeveloperUILink, useHasDeveloperUi} from '../../utils/developerUI/developerUI';
8
+ import {formatPDiskType} from '../../utils/disks/getPDiskType';
8
9
  import type {PreparedPDisk} from '../../utils/disks/types';
9
10
  import {getPDiskCapacityInfoItems, toInfoViewerItems} from '../DiskCapacityInfo/DiskCapacityInfo';
10
11
  import type {InfoViewerItem} from '../InfoViewer';
@@ -57,7 +58,7 @@ function getPDiskInfo<T extends PreparedPDisk>({
57
58
  const generalInfo: InfoViewerItem[] = [];
58
59
 
59
60
  if (valueIsDefined(Category)) {
60
- generalInfo.push({label: pDiskInfoKeyset('type'), value: Type});
61
+ generalInfo.push({label: pDiskInfoKeyset('type'), value: formatPDiskType(Type)});
61
62
  }
62
63
  if (valueIsDefined(Path)) {
63
64
  generalInfo.push({label: pDiskInfoKeyset('path'), value: Path});
@@ -12,6 +12,7 @@ import {BRAND_BUTTON_CLASS, EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'
12
12
  import {createPDiskDeveloperUILink, useHasDeveloperUi} from '../../utils/developerUI/developerUI';
13
13
  import {getStateSeverity} from '../../utils/disks/calculatePDiskSeverity';
14
14
  import {NUMERIC_SEVERITY_TO_LABEL_VIEW} from '../../utils/disks/constants';
15
+ import {formatPDiskType} from '../../utils/disks/getPDiskType';
15
16
  import type {PreparedPDisk} from '../../utils/disks/types';
16
17
  import {useTypedSelector} from '../../utils/hooks';
17
18
  import {useDatabaseFromQuery} from '../../utils/hooks/useDatabaseFromQuery';
@@ -41,7 +42,10 @@ export const preparePDiskData = (
41
42
  const {AvailableSize, TotalSize, NodeId, Path, Realtime, Type, Device} = data;
42
43
 
43
44
  const pdiskData: YDBDefinitionListItem[] = [
44
- {name: pDiskPopupKeyset('label_type'), content: Type || pDiskPopupKeyset('value_unknown')},
45
+ {
46
+ name: pDiskPopupKeyset('label_type'),
47
+ content: formatPDiskType(Type) || pDiskPopupKeyset('value_unknown'),
48
+ },
45
49
  ];
46
50
 
47
51
  if (NodeId) {
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
3
  import isEqual from 'lodash/isEqual';
4
+ import omit from 'lodash/omit';
4
5
  import {useStore} from 'react-redux';
5
6
 
6
7
  import type {RootState} from '../../store';
@@ -78,14 +79,7 @@ export const PaginatedTable = <T, F>({
78
79
  }: PaginatedTableProps<T, F>) => {
79
80
  const store = useStore<RootState>();
80
81
  // Get state and setters from context
81
- const {
82
- tableState,
83
- noBatching,
84
- setSortParams,
85
- setTotalEntities,
86
- setFoundEntities,
87
- setIsInitialLoad,
88
- } = usePaginatedTableState();
82
+ const {tableState, noBatching, setSortParams, dispatchCounts} = usePaginatedTableState();
89
83
 
90
84
  const {sortParams, foundEntities} = tableState;
91
85
  const activeSortParams = isSortColumnAvailable(sortParams, columns) ? sortParams : undefined;
@@ -100,6 +94,27 @@ export const PaginatedTable = <T, F>({
100
94
  setFilters(rawFilters);
101
95
  }, [rawFilters]);
102
96
 
97
+ const nextCountsQuery = omit(
98
+ getTableChunkQueryParams({
99
+ offset: 0,
100
+ limit: chunkSize,
101
+ fetchData,
102
+ filters,
103
+ sortParams: activeSortParams,
104
+ columns,
105
+ tableName,
106
+ noBatching,
107
+ }),
108
+ 'offset',
109
+ 'fetchData',
110
+ );
111
+ const [countsQuery, setCountsQuery] = React.useState(nextCountsQuery);
112
+
113
+ // Resizing columns must not replay cached chunk responses through a new callback.
114
+ if (!isEqual(countsQuery, nextCountsQuery)) {
115
+ setCountsQuery(nextCountsQuery);
116
+ }
117
+
103
118
  const getQueryParams = (offset: number) =>
104
119
  getTableChunkQueryParams({
105
120
  offset,
@@ -186,15 +201,16 @@ export const PaginatedTable = <T, F>({
186
201
  };
187
202
 
188
203
  const handleDataFetched = React.useCallback(
189
- (data?: PaginatedTableData<T>) => {
190
- if (data) {
191
- setTotalEntities(data.total);
192
- setFoundEntities(data.found);
193
- setIsInitialLoad(false);
194
- onDataFetched?.(data);
195
- }
204
+ (data: PaginatedTableData<T>) => {
205
+ dispatchCounts({
206
+ type: 'dataReceived',
207
+ query: countsQuery,
208
+ total: data.total,
209
+ found: data.found,
210
+ });
211
+ onDataFetched?.(data);
196
212
  },
197
- [onDataFetched, setFoundEntities, setIsInitialLoad, setTotalEntities],
213
+ [countsQuery, dispatchCounts, onDataFetched],
198
214
  );
199
215
 
200
216
  // Set will-change: transform on scroll container if not already set
@@ -208,15 +224,9 @@ export const PaginatedTable = <T, F>({
208
224
  }
209
225
  }, [scrollContainerRef.current]);
210
226
 
211
- // Reset table on initialization and filters change
212
227
  React.useLayoutEffect(() => {
213
- const defaultTotal = initialEntitiesCount || 0;
214
- const defaultFound = initialEntitiesCount || 1;
215
-
216
- setTotalEntities(defaultTotal);
217
- setFoundEntities(defaultFound);
218
- setIsInitialLoad(true);
219
- }, [initialEntitiesCount, setTotalEntities, setFoundEntities, setIsInitialLoad]);
228
+ dispatchCounts({type: 'initialize', query: countsQuery, initialEntitiesCount});
229
+ }, [countsQuery, initialEntitiesCount, dispatchCounts]);
220
230
 
221
231
  const renderTable = () => (
222
232
  <table className={b('table')}>
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
+ import type {TableCountsAction} from './tableCounts';
4
+ import {tableCountsReducer} from './tableCounts';
3
5
  import type {PaginatedTableState} from './types';
4
6
 
5
7
  // Default state for the table
@@ -18,18 +20,14 @@ interface PaginatedTableStateContextType {
18
20
 
19
21
  // Granular setters
20
22
  setSortParams: (params: PaginatedTableState['sortParams']) => void;
21
- setTotalEntities: (total: number) => void;
22
- setFoundEntities: (found: number) => void;
23
- setIsInitialLoad: (isInitial: boolean) => void;
23
+ dispatchCounts: React.Dispatch<TableCountsAction>;
24
24
  }
25
25
 
26
26
  // Creating the context with default values
27
27
  export const PaginatedTableStateContext = React.createContext<PaginatedTableStateContextType>({
28
28
  tableState: defaultTableState,
29
29
  setSortParams: () => undefined,
30
- setTotalEntities: () => undefined,
31
- setFoundEntities: () => undefined,
32
- setIsInitialLoad: () => undefined,
30
+ dispatchCounts: () => undefined,
33
31
  });
34
32
 
35
33
  // Provider component props
@@ -45,18 +43,17 @@ export const PaginatedTableProvider = ({
45
43
  initialState = {},
46
44
  noBatching,
47
45
  }: PaginatedTableStateProviderProps) => {
48
- // Use individual state variables for each field
49
46
  const [sortParams, setSortParams] = React.useState<PaginatedTableState['sortParams']>(
50
47
  initialState.sortParams ?? defaultTableState.sortParams,
51
48
  );
52
- const [totalEntities, setTotalEntities] = React.useState<number>(
53
- initialState.totalEntities ?? defaultTableState.totalEntities,
54
- );
55
- const [foundEntities, setFoundEntities] = React.useState<number>(
56
- initialState.foundEntities ?? defaultTableState.foundEntities,
57
- );
58
- const [isInitialLoad, setIsInitialLoad] = React.useState<boolean>(
59
- initialState.isInitialLoad ?? defaultTableState.isInitialLoad,
49
+ const [{totalEntities, foundEntities, isInitialLoad}, dispatchCounts] = React.useReducer(
50
+ tableCountsReducer,
51
+ {
52
+ totalEntities: initialState.totalEntities ?? defaultTableState.totalEntities,
53
+ foundEntities: initialState.foundEntities ?? defaultTableState.foundEntities,
54
+ isInitialLoad: initialState.isInitialLoad ?? defaultTableState.isInitialLoad,
55
+ hasLoadedData: initialState.isInitialLoad === false,
56
+ },
60
57
  );
61
58
 
62
59
  // Construct tableState from individual state variables
@@ -70,15 +67,13 @@ export const PaginatedTableProvider = ({
70
67
  [sortParams, totalEntities, foundEntities, isInitialLoad],
71
68
  );
72
69
 
73
- // Create the context value with the constructed tableState and direct setters
70
+ // Create the context value with the constructed tableState and its update functions
74
71
  const contextValue = React.useMemo(
75
72
  () => ({
76
73
  tableState,
77
74
  noBatching,
78
75
  setSortParams,
79
- setTotalEntities,
80
- setFoundEntities,
81
- setIsInitialLoad,
76
+ dispatchCounts,
82
77
  }),
83
78
  [tableState, noBatching],
84
79
  );
@@ -42,7 +42,7 @@ interface TableChunkProps<T, F> {
42
42
  onRowClick?: OnRowClick<T>;
43
43
  renderErrorMessage?: RenderErrorMessage;
44
44
  renderEmptyDataMessage?: RenderEmptyDataMessage;
45
- onDataFetched: (data?: PaginatedTableData<T>) => void;
45
+ onDataFetched: (data: PaginatedTableData<T>) => void;
46
46
 
47
47
  keepCache?: boolean;
48
48
  }
@@ -104,8 +104,9 @@ export const TableChunk = typedMemo(function TableChunk<T, F>({
104
104
  };
105
105
  }, [shouldFetch, isTimeoutActive]);
106
106
 
107
- React.useEffect(() => {
107
+ React.useLayoutEffect(() => {
108
108
  if (currentData) {
109
+ // Consumers derive row dimensions from the response before loaded rows are painted.
109
110
  onDataFetched({
110
111
  ...currentData,
111
112
  data: currentData.data as T[],
@@ -30,7 +30,7 @@ export interface TableChunksRendererProps<T, F> {
30
30
  onRowClick?: OnRowClick<T>;
31
31
  renderErrorMessage?: RenderErrorMessage;
32
32
  renderEmptyDataMessage?: RenderEmptyDataMessage;
33
- onDataFetched: (data?: PaginatedTableData<T>) => void;
33
+ onDataFetched: (data: PaginatedTableData<T>) => void;
34
34
  onActiveChunkOffsetsChange: (offsets: number[]) => void;
35
35
  keepCache: boolean;
36
36
  fetchOverscan?: number;
@@ -0,0 +1,50 @@
1
+ import isEqual from 'lodash/isEqual';
2
+
3
+ import type {getTableChunkQueryParams} from './getTableChunkQueryParams';
4
+ import type {PaginatedTableState} from './types';
5
+
6
+ type TableQuery = Omit<ReturnType<typeof getTableChunkQueryParams>, 'offset' | 'fetchData'>;
7
+
8
+ export interface TableCountsState
9
+ extends Pick<PaginatedTableState, 'totalEntities' | 'foundEntities' | 'isInitialLoad'> {
10
+ query?: TableQuery;
11
+ hasLoadedData: boolean;
12
+ }
13
+
14
+ export type TableCountsAction =
15
+ | {type: 'initialize'; query: TableQuery; initialEntitiesCount?: number}
16
+ | {type: 'dataReceived'; query: TableQuery; total: number; found: number};
17
+
18
+ export function tableCountsReducer(
19
+ state: TableCountsState,
20
+ action: TableCountsAction,
21
+ ): TableCountsState {
22
+ // Cached chunks can report their data before the table's initialization effect.
23
+ if (
24
+ action.type === 'initialize' &&
25
+ isEqual(state.query, action.query) &&
26
+ !state.isInitialLoad
27
+ ) {
28
+ return state;
29
+ }
30
+
31
+ let nextState: TableCountsState;
32
+ if (action.type === 'dataReceived') {
33
+ nextState = {
34
+ query: action.query,
35
+ totalEntities: action.total,
36
+ foundEntities: action.found,
37
+ isInitialLoad: false,
38
+ hasLoadedData: true,
39
+ };
40
+ } else {
41
+ // Keep the virtualized table's size until the next query returns its actual counts.
42
+ nextState = {...state, query: action.query, isInitialLoad: true};
43
+ if (!state.hasLoadedData) {
44
+ nextState.totalEntities = action.initialEntitiesCount ?? 0;
45
+ nextState.foundEntities = action.initialEntitiesCount ?? 1;
46
+ }
47
+ }
48
+
49
+ return isEqual(state, nextState) ? state : nextState;
50
+ }
@@ -13,6 +13,7 @@ import type {
13
13
  } from '../../utils/disks/displayState';
14
14
  import {getDefaultDiskDisplayState} from '../../utils/disks/displayState';
15
15
  import {getDiskBarTone} from '../../utils/disks/getDiskBarTone';
16
+ import {formatPDiskType} from '../../utils/disks/getPDiskType';
16
17
  import {getVDiskStatusIcon} from '../../utils/disks/helpers';
17
18
  import type {PreparedVDisk} from '../../utils/disks/types';
18
19
  import {isNumeric} from '../../utils/utils';
@@ -162,7 +163,10 @@ function getAllModeAccessibleName(data: PreparedVDisk, hasIssues: boolean | unde
162
163
  });
163
164
  }
164
165
 
165
- function getAccessibleName(data: PreparedVDisk, {mode, allMode, isNoData}: VDiskDisplayState) {
166
+ function getAccessibleName(
167
+ data: PreparedVDisk,
168
+ {mode, allMode, isNoData, driveType}: VDiskDisplayState,
169
+ ) {
166
170
  if (!mode) {
167
171
  return undefined;
168
172
  }
@@ -176,13 +180,24 @@ function getAccessibleName(data: PreparedVDisk, {mode, allMode, isNoData}: VDisk
176
180
  nodeId: data.NodeId ?? noData,
177
181
  });
178
182
  if (isNoData) {
179
- diskName = i18n('context_vdisk-no-whiteboard', {disk: diskName, noData});
183
+ const hasVDiskWhiteboardData = data.HasWhiteboardData ?? Boolean(data.VDiskId);
184
+ diskName = i18n(
185
+ mode === 'driveType' && hasVDiskWhiteboardData
186
+ ? 'context_pdisk-no-whiteboard'
187
+ : 'context_vdisk-no-whiteboard',
188
+ {disk: diskName, noData},
189
+ );
180
190
  }
181
191
 
182
192
  const {CapacityAlert, FrontQueues, SatisfactionRank, Replicated} = isNoData ? {} : data;
183
193
  const {FreshRank, LevelRank} = SatisfactionRank ?? {};
184
194
 
185
195
  switch (mode) {
196
+ case 'driveType':
197
+ return i18n('context_drive-type-accessible-name', {
198
+ disk: diskName,
199
+ driveType: formatPDiskType(driveType) || noData,
200
+ });
186
201
  case 'state':
187
202
  return i18n('context_state-accessible-name', {
188
203
  disk: diskName,
@@ -230,6 +245,7 @@ export interface VDiskProps {
230
245
  popupOffset?: PopupProps['offset'];
231
246
  withOpaqueBackground?: boolean;
232
247
  getDisplayState?: VDiskDisplayStateGetter;
248
+ hidePDiskInPopup?: boolean;
233
249
  }
234
250
 
235
251
  export const VDisk = ({
@@ -252,6 +268,7 @@ export const VDisk = ({
252
268
  popupOffset = DEFAULT_POPUP_OFFSET,
253
269
  withOpaqueBackground,
254
270
  getDisplayState,
271
+ hidePDiskInPopup,
255
272
  }: VDiskProps) => {
256
273
  const getVDiskLink = useVDiskPagePath();
257
274
  const vDiskPath = getVDiskLink({nodeId: data.NodeId, vDiskId: data.StringifiedId});
@@ -285,7 +302,7 @@ export const VDisk = ({
285
302
  indicatorClassName,
286
303
  iconGroupSize,
287
304
  iconSize,
288
- isDonor,
305
+ isDonor: mode !== 'driveType' && isDonor,
289
306
  placement: iconPlacement,
290
307
  severity,
291
308
  withIcon,
@@ -307,8 +324,9 @@ export const VDisk = ({
307
324
  />
308
325
  ) : null;
309
326
  const tone = getDiskBarTone({
327
+ driveType: displayState.driveType,
310
328
  severity,
311
- isDonor,
329
+ isDonor: mode !== 'driveType' && isDonor,
312
330
  showIndicator,
313
331
  indicator: icon,
314
332
  isNoData,
@@ -319,7 +337,12 @@ export const VDisk = ({
319
337
  showPopup={showPopup}
320
338
  onShowPopup={onShowPopup}
321
339
  onHidePopup={onHidePopup}
322
- renderPopupContent={({onClose}) => <VDiskPopup data={data} onClose={onClose} />}
340
+ renderPopupContent={({onClose}) => (
341
+ <VDiskPopup
342
+ data={hidePDiskInPopup ? {...data, PDisk: undefined} : data}
343
+ onClose={onClose}
344
+ />
345
+ )}
323
346
  offset={popupOffset}
324
347
  delayClose={delayClose}
325
348
  delayOpen={delayOpen}
@@ -8,7 +8,8 @@ import {Stack} from '../Stack/Stack';
8
8
  import type {VDiskProps} from './VDisk';
9
9
  import {VDisk} from './VDisk';
10
10
 
11
- interface VDiskWithDonorsStackProps extends Omit<VDiskProps, 'withOpaqueBackground'> {
11
+ interface VDiskWithDonorsStackProps
12
+ extends Omit<VDiskProps, 'withOpaqueBackground' | 'hidePDiskInPopup'> {
12
13
  data?: PreparedVDisk;
13
14
  className?: string;
14
15
  stackClassName?: string;
@@ -17,6 +18,7 @@ interface VDiskWithDonorsStackProps extends Omit<VDiskProps, 'withOpaqueBackgrou
17
18
  progressBarClassName?: string;
18
19
  renderContent?: boolean;
19
20
  placeholderProps?: React.HTMLAttributes<HTMLDivElement>;
21
+ hideMainPDiskInPopup?: boolean;
20
22
  }
21
23
 
22
24
  const diskInStackPlacement: PopupPlacement = ['left', 'right'];
@@ -29,6 +31,7 @@ export function VDiskWithDonorsStack({
29
31
  stackClassName,
30
32
  withIcon,
31
33
  compact,
34
+ hideMainPDiskInPopup,
32
35
  highlightedVDisk,
33
36
  setHighlightedVDisk,
34
37
  showPopup: _showPopup,
@@ -81,6 +84,7 @@ export function VDiskWithDonorsStack({
81
84
  const mainVDiskProps: Partial<VDiskProps> = {
82
85
  ...restProps,
83
86
  compact,
87
+ hidePDiskInPopup: hideMainPDiskInPopup,
84
88
  withIcon,
85
89
  showPopup: isHighlighted,
86
90
  highlighted: isHighlighted,
@@ -3,6 +3,7 @@
3
3
  "context_vdisk": "VDisk {{vdiskId}} on node {{nodeId}}",
4
4
  "context_donor-vdisk": "Donor VDisk {{vdiskId}} on node {{nodeId}}",
5
5
  "context_vdisk-no-whiteboard": "{{disk}}. Whiteboard: {{noData}}",
6
+ "context_pdisk-no-whiteboard": "{{disk}}. PDisk Whiteboard: {{noData}}",
6
7
  "context_state-accessible-name": "{{disk}}. State: {{state}}. Replication: {{replication}}.",
7
8
  "context_space-accessible-name": "{{disk}}. Capacity alert: {{capacityAlert}}.",
8
9
  "context_front-queues-accessible-name": "{{disk}}. Front queues: {{frontQueues}}.",
@@ -15,5 +16,6 @@
15
16
  "context_all-mode-health-issues": "issues detected",
16
17
  "context_all-mode-replication-complete": "complete",
17
18
  "context_all-mode-replication-in-progress": "in progress",
18
- "context_all-mode-accessible-name": "VDisk {{vdiskId}}. Health: {{health}}. State: {{state}}. Replication: {{replication}}. Capacity alert: {{capacityAlert}}. Front queues: {{frontQueues}}. Fresh compaction: {{freshCompaction}}. Level compaction: {{levelCompaction}}. Allocated: {{allocatedPercent}}."
19
+ "context_all-mode-accessible-name": "VDisk {{vdiskId}}. Health: {{health}}. State: {{state}}. Replication: {{replication}}. Capacity alert: {{capacityAlert}}. Front queues: {{frontQueues}}. Fresh compaction: {{freshCompaction}}. Level compaction: {{levelCompaction}}. Allocated: {{allocatedPercent}}.",
20
+ "context_drive-type-accessible-name": "{{disk}}. Drive type: {{driveType}}."
19
21
  }
@@ -66,9 +66,6 @@ const VDiskItem = React.memo(function VDiskItem({
66
66
  renderContent,
67
67
  placeholderProps,
68
68
  }: DisksItemProps) {
69
- // Do not show PDisk popup for VDisk
70
- const vDiskToShow = React.useMemo(() => ({...vDisk, PDisk: undefined}), [vDisk]);
71
-
72
69
  const style: React.CSSProperties = isAllVDisksLayout
73
70
  ? {width: ALL_VDISK_WIDTH, flexBasis: ALL_VDISK_WIDTH}
74
71
  : {width: compactVDiskWidth, flexBasis: compactVDiskWidth};
@@ -92,7 +89,8 @@ const VDiskItem = React.memo(function VDiskItem({
92
89
  <VDiskWithDonorsStack
93
90
  renderContent={renderContent}
94
91
  placeholderProps={placeholderProps}
95
- data={vDiskToShow}
92
+ data={vDisk}
93
+ hideMainPDiskInPopup
96
94
  compact={!isAllVDisksLayout}
97
95
  withIcon={withIcon}
98
96
  inactive={inactive}
@@ -20,6 +20,7 @@ import type {
20
20
  } from '../../../utils/disks/displayState';
21
21
  import {getDefaultPDiskDisplayState} from '../../../utils/disks/displayState';
22
22
  import {getDiskBarTone} from '../../../utils/disks/getDiskBarTone';
23
+ import {formatPDiskType} from '../../../utils/disks/getPDiskType';
23
24
  import {getPDiskId, getVDiskStatusIcon} from '../../../utils/disks/helpers';
24
25
  import type {PreparedPDisk} from '../../../utils/disks/types';
25
26
  import {isNumeric} from '../../../utils/utils';
@@ -78,7 +79,9 @@ function getPDiskContent({
78
79
  type={
79
80
  <React.Fragment>
80
81
  {showNoDataLabel ? barContent : null}
81
- <DiskBarLabel>{data.Type || EMPTY_DATA_PLACEHOLDER}</DiskBarLabel>
82
+ <DiskBarLabel>
83
+ {formatPDiskType(data.Type) || EMPTY_DATA_PLACEHOLDER}
84
+ </DiskBarLabel>
82
85
  </React.Fragment>
83
86
  }
84
87
  />
@@ -191,7 +194,7 @@ function getAllModeAccessibleName(
191
194
 
192
195
  function getAccessibleName(
193
196
  data: PreparedPDisk,
194
- {mode, allMode, isNoData, allocatedPercent}: PDiskDisplayState,
197
+ {mode, allMode, isNoData, allocatedPercent, driveType}: PDiskDisplayState,
195
198
  ) {
196
199
  if (!mode) {
197
200
  return undefined;
@@ -208,6 +211,11 @@ function getAccessibleName(
208
211
  const {State, Device, Realtime} = isNoData ? {} : data;
209
212
 
210
213
  switch (mode) {
214
+ case 'driveType':
215
+ return i18n('context_drive-type-accessible-name', {
216
+ disk: diskName,
217
+ driveType: formatPDiskType(driveType) || noData,
218
+ });
211
219
  case 'state':
212
220
  return i18n('context_state-accessible-name', {disk: diskName, state: State || noData});
213
221
  case 'space':
@@ -325,6 +333,7 @@ export const PDisk = ({
325
333
  });
326
334
 
327
335
  const tone = getDiskBarTone({
336
+ driveType: displayState.driveType,
328
337
  severity: displayState.severity,
329
338
  showIndicator,
330
339
  indicator: displayState.icon,
@@ -1,4 +1,5 @@
1
1
  {
2
+ "context_drive-type-accessible-name": "{{disk}}. Drive type: {{driveType}}.",
2
3
  "context_no-data": "N/D",
3
4
  "context_pdisk": "PDisk {{pdiskId}}",
4
5
  "context_pdisk-no-whiteboard": "{{disk}}. Whiteboard: {{noData}}",
@@ -17,6 +17,7 @@ function getVDiskOptions(): ExpertModeOption<VDisksGroupByValue>[] {
17
17
  {value: VDisksGroupBy.Space, content: i18n('value_space')},
18
18
  {value: VDisksGroupBy.FrontQueues, content: i18n('value_front-queues')},
19
19
  {value: VDisksGroupBy.Compaction, content: i18n('value_compaction')},
20
+ {value: VDisksGroupBy.DriveType, content: i18n('value_drive-type')},
20
21
  {value: VDisksGroupBy.All, content: i18n('value_all')},
21
22
  ];
22
23
  }
@@ -29,6 +30,7 @@ function getPDiskOptions(): ExpertModeOption<PDisksGroupByValue>[] {
29
30
  {value: PDisksGroupBy.Decommit, content: i18n('value_pdisks-decommit')},
30
31
  {value: PDisksGroupBy.Maintenance, content: i18n('value_pdisks-maintenance')},
31
32
  {value: PDisksGroupBy.Device, content: i18n('value_pdisks-device')},
33
+ {value: PDisksGroupBy.DriveType, content: i18n('value_drive-type')},
32
34
  {value: PDisksGroupBy.All, content: i18n('value_pdisks-all')},
33
35
  ];
34
36
  }
@@ -0,0 +1,22 @@
1
+ import {Flex, Label} from '@gravity-ui/uikit';
2
+
3
+ import i18n from '../i18n';
4
+
5
+ export function DriveTypeLegend() {
6
+ return (
7
+ <Flex gap={2} alignItems="center" wrap="wrap">
8
+ <Label size="xs" theme="normal">
9
+ {i18n('value_ssd')}
10
+ </Label>
11
+ <Label size="xs" theme="utility">
12
+ {i18n('value_hdd')}
13
+ </Label>
14
+ <Label size="xs" theme="info">
15
+ {i18n('value_nvme')}
16
+ </Label>
17
+ <Label size="xs" theme="unknown">
18
+ {i18n('value_no-data')}
19
+ </Label>
20
+ </Flex>
21
+ );
22
+ }