ydb-embedded-ui 4.18.0 → 4.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/components/FullNodeViewer/FullNodeViewer.scss +1 -1
  3. package/dist/components/FullNodeViewer/FullNodeViewer.tsx +13 -4
  4. package/dist/components/ProgressViewer/ProgressViewer.tsx +11 -5
  5. package/dist/containers/Nodes/getNodesColumns.tsx +1 -0
  6. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +1 -1
  7. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -232
  8. package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +278 -0
  9. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +2 -3
  10. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +22 -6
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +41 -0
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +68 -0
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +76 -0
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +105 -0
  15. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +2 -0
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +2 -0
  17. package/dist/containers/Versions/NodesTable/NodesTable.tsx +1 -0
  18. package/dist/store/reducers/index.ts +4 -0
  19. package/dist/store/reducers/storage/utils.ts +20 -11
  20. package/dist/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts +93 -0
  21. package/dist/store/reducers/tenantOverview/executeTopTables/types.ts +14 -0
  22. package/dist/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts +98 -0
  23. package/dist/store/reducers/tenantOverview/topStorageGroups/types.ts +29 -0
  24. package/dist/store/reducers/tenantOverview/topStorageGroups/utils.ts +20 -0
  25. package/dist/store/reducers/tenants/utils.ts +28 -18
  26. package/dist/styles/constants.scss +4 -0
  27. package/dist/types/additionalProps.ts +1 -1
  28. package/dist/types/api/storage.ts +1 -1
  29. package/dist/types/api/tenant.ts +21 -8
  30. package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +10 -1
  31. package/dist/utils/bytesParsers/formatBytes.ts +3 -3
  32. package/dist/utils/constants.ts +8 -0
  33. package/dist/utils/dataFormatters/__test__/roundToSignificant.test.ts +22 -0
  34. package/dist/utils/dataFormatters/dataFormatters.ts +46 -20
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.19.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.19.0...v4.19.1) (2023-10-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add storage value to tb formatter ([#547](https://github.com/ydb-platform/ydb-embedded-ui/issues/547)) ([f1e4377](https://github.com/ydb-platform/ydb-embedded-ui/commit/f1e4377443be493a7072aca33a62b51e381f6841))
9
+
10
+ ## [4.19.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.18.0...v4.19.0) (2023-10-11)
11
+
12
+
13
+ ### Features
14
+
15
+ * **TenantOverview:** add storage tab to tenant diagnostics ([#541](https://github.com/ydb-platform/ydb-embedded-ui/issues/541)) ([c4cdd35](https://github.com/ydb-platform/ydb-embedded-ui/commit/c4cdd354cd9780dfd7dfee80ec225f59d4230625))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * add NodeId to NodeAddress type ([#545](https://github.com/ydb-platform/ydb-embedded-ui/issues/545)) ([3df82d3](https://github.com/ydb-platform/ydb-embedded-ui/commit/3df82d39466696ec61e34b915b355dacd0482ebc))
21
+ * display database name in node info ([#543](https://github.com/ydb-platform/ydb-embedded-ui/issues/543)) ([788ad9a](https://github.com/ydb-platform/ydb-embedded-ui/commit/788ad9a7a1a56ffe93ec7e4861ded6cceef72d9c))
22
+ * fix cpu usage calculation ([#542](https://github.com/ydb-platform/ydb-embedded-ui/issues/542)) ([f46b03d](https://github.com/ydb-platform/ydb-embedded-ui/commit/f46b03d6157f19017560d71a9ab6591f045bad96))
23
+ * fix incorrect data display in ProgressViewer ([#546](https://github.com/ydb-platform/ydb-embedded-ui/issues/546)) ([be077b8](https://github.com/ydb-platform/ydb-embedded-ui/commit/be077b83a4b4cf083d506e77abf0f2b6570c87d3))
24
+
3
25
  ## [4.18.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.17.0...v4.18.0) (2023-09-25)
4
26
 
5
27
 
@@ -23,7 +23,7 @@
23
23
  }
24
24
 
25
25
  .info-viewer__label {
26
- min-width: 60px;
26
+ min-width: 100px;
27
27
  }
28
28
 
29
29
  &__section-title {
@@ -5,7 +5,7 @@ import type {TSystemStateInfo} from '../../types/api/nodes';
5
5
  import {LOAD_AVERAGE_TIME_INTERVALS} from '../../utils/constants';
6
6
  import {calcUptime} from '../../utils/dataFormatters/dataFormatters';
7
7
 
8
- import InfoViewer from '../InfoViewer/InfoViewer';
8
+ import InfoViewer, {type InfoViewerItem} from '../InfoViewer/InfoViewer';
9
9
  import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
10
10
  import {PoolUsage} from '../PoolUsage/PoolUsage';
11
11
 
@@ -24,16 +24,25 @@ export const FullNodeViewer = ({node, className}: FullNodeViewerProps) => {
24
24
  value: Address,
25
25
  }));
26
26
 
27
- const commonInfo = [
27
+ const commonInfo: InfoViewerItem[] = [];
28
+
29
+ // Do not add DB field for static nodes (they have no Tenants)
30
+ if (node?.Tenants?.length) {
31
+ commonInfo.push({label: 'Database', value: node.Tenants[0]});
32
+ }
33
+
34
+ commonInfo.push(
28
35
  {label: 'Version', value: node?.Version},
29
36
  {label: 'Uptime', value: calcUptime(node?.StartTime)},
30
37
  {label: 'DC', value: node?.DataCenterDescription},
31
38
  {label: 'Rack', value: node?.Rack},
32
- ];
39
+ );
33
40
 
34
41
  const averageInfo = node?.LoadAverage?.map((load, loadIndex) => ({
35
42
  label: LOAD_AVERAGE_TIME_INTERVALS[loadIndex],
36
- value: <ProgressViewer value={load} percents={true} colorizeProgress={true} />,
43
+ value: (
44
+ <ProgressViewer value={load} percents={true} colorizeProgress={true} capacity={100} />
45
+ ),
37
46
  }));
38
47
 
39
48
  return (
@@ -46,7 +46,7 @@ interface ProgressViewerProps {
46
46
 
47
47
  export function ProgressViewer({
48
48
  value,
49
- capacity = 100,
49
+ capacity,
50
50
  formatValues,
51
51
  percents,
52
52
  className,
@@ -84,13 +84,19 @@ export function ProgressViewer({
84
84
 
85
85
  const text = fillWidth > 60 ? 'contrast0' : 'contrast70';
86
86
 
87
- if (!isNaN(fillWidth)) {
87
+ const renderContent = () => {
88
+ if (capacityText) {
89
+ return `${valueText} ${divider} ${capacityText}`;
90
+ }
91
+
92
+ return valueText;
93
+ };
94
+
95
+ if (!isNaN(Number(value))) {
88
96
  return (
89
97
  <div className={b({size}, className)}>
90
98
  <div className={b('line', {bg})} style={lineStyle}></div>
91
- <span
92
- className={b('text', {text})}
93
- >{`${valueText} ${divider} ${capacityText}`}</span>
99
+ <span className={b('text', {text})}>{renderContent()}</span>
94
100
  </div>
95
101
  );
96
102
  }
@@ -120,6 +120,7 @@ export function getNodesColumns({
120
120
  row.LoadAverage && row.LoadAverage.length > 0 ? (
121
121
  <ProgressViewer
122
122
  value={row.LoadAverage[0]}
123
+ capacity={100}
123
124
  percents={true}
124
125
  colorizeProgress={true}
125
126
  />
@@ -23,7 +23,7 @@ export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesMap) => {
23
23
  const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Device} = data;
24
24
 
25
25
  const pdiskData: InfoViewerItem[] = [
26
- {label: 'PDisk', value: getPDiskId({NodeId, PDiskId})},
26
+ {label: 'PDisk', value: getPDiskId({NodeId, PDiskId}) || '-'},
27
27
  {label: 'State', value: State || 'not available'},
28
28
  {label: 'Type', value: getPDiskType(data) || 'unknown'},
29
29
  ];
@@ -1,25 +1,12 @@
1
- import cn from 'bem-cn-lite';
2
-
3
1
  import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
4
- import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
2
 
6
- import type {ValueOf} from '../../../types/common';
7
3
  import type {NodesMap} from '../../../types/store/nodesList';
8
4
  import type {PreparedStorageGroup, VisibleEntities} from '../../../store/reducers/storage/types';
9
5
  import type {HandleSort} from '../../../utils/hooks/useTableSort';
10
-
11
6
  import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants';
12
- import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
13
- import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
14
- import {getUsage, isFullVDiskData, isSortableStorageProperty} from '../../../utils/storage';
15
-
16
- import shieldIcon from '../../../assets/icons/shield.svg';
17
- import {Stack} from '../../../components/Stack/Stack';
18
- import EntityStatus from '../../../components/EntityStatus/EntityStatus';
19
-
7
+ import {isSortableStorageProperty} from '../../../utils/storage';
20
8
  import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
21
- import {VDisk} from '../VDisk';
22
- import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
9
+ import {getStorageGroupsColumns} from './getStorageGroupsColumns';
23
10
 
24
11
  import i18n from './i18n';
25
12
  import './StorageGroups.scss';
@@ -39,8 +26,6 @@ const TableColumnsIds = {
39
26
  Degraded: 'Degraded',
40
27
  } as const;
41
28
 
42
- type TableColumnId = ValueOf<typeof TableColumnsIds>;
43
-
44
29
  interface StorageGroupsProps {
45
30
  data: PreparedStorageGroup[];
46
31
  nodes?: NodesMap;
@@ -51,23 +36,6 @@ interface StorageGroupsProps {
51
36
  handleSort?: HandleSort;
52
37
  }
53
38
 
54
- const tableColumnsNames: Record<TableColumnId, string> = {
55
- PoolName: 'Pool Name',
56
- Kind: 'Type',
57
- Erasure: 'Erasure',
58
- GroupId: 'Group ID',
59
- Used: 'Used',
60
- Limit: 'Limit',
61
- UsedSpaceFlag: 'Space',
62
- Usage: 'Usage',
63
- Read: 'Read',
64
- Write: 'Write',
65
- VDisks: 'VDisks',
66
- Degraded: 'Degraded',
67
- };
68
-
69
- const b = cn('global-storage-groups');
70
-
71
39
  export function StorageGroups({
72
40
  data,
73
41
  tableSettings,
@@ -77,204 +45,7 @@ export function StorageGroups({
77
45
  sort,
78
46
  handleSort,
79
47
  }: StorageGroupsProps) {
80
- const rawColumns: Column<PreparedStorageGroup>[] = [
81
- {
82
- name: TableColumnsIds.PoolName,
83
- header: tableColumnsNames[TableColumnsIds.PoolName],
84
- width: 250,
85
- render: ({row}) => {
86
- const splitted = row.PoolName?.split('/');
87
- return (
88
- <div className={b('pool-name-wrapper')}>
89
- {splitted && (
90
- <Popover
91
- content={row.PoolName}
92
- placement={['right']}
93
- behavior={PopoverBehavior.Immediate}
94
- >
95
- <span className={b('pool-name')}>
96
- {splitted[splitted.length - 1]}
97
- </span>
98
- </Popover>
99
- )}
100
- </div>
101
- );
102
- },
103
- align: DataTable.LEFT,
104
- },
105
- {
106
- name: TableColumnsIds.Kind,
107
- header: tableColumnsNames[TableColumnsIds.Kind],
108
- // prettier-ignore
109
- render: ({row}) => (
110
- <>
111
- <Label>{row.Kind || '—'}</Label>
112
- {' '}
113
- {row.Encryption && (
114
- <Popover
115
- content={i18n('encrypted')}
116
- placement="right"
117
- behavior={PopoverBehavior.Immediate}
118
- >
119
- <Label>
120
- <Icon data={shieldIcon} />
121
- </Label>
122
- </Popover>
123
- )}
124
- </>
125
- ),
126
- },
127
- {
128
- name: TableColumnsIds.Erasure,
129
- header: tableColumnsNames[TableColumnsIds.Erasure],
130
- render: ({row}) => (row.ErasureSpecies ? row.ErasureSpecies : '-'),
131
- align: DataTable.LEFT,
132
- },
133
- {
134
- name: TableColumnsIds.Degraded,
135
- header: tableColumnsNames[TableColumnsIds.Degraded],
136
- width: 100,
137
- render: ({row}) =>
138
- row.Degraded ? (
139
- <Label theme={getDegradedSeverity(row)}>Degraded: {row.Degraded}</Label>
140
- ) : (
141
- '-'
142
- ),
143
- align: DataTable.LEFT,
144
- defaultOrder: DataTable.DESCENDING,
145
- },
146
- {
147
- name: TableColumnsIds.Usage,
148
- header: tableColumnsNames[TableColumnsIds.Usage],
149
- width: 100,
150
- render: ({row}) => {
151
- // without a limit the usage can be evaluated as 0,
152
- // but the absence of a value is more clear
153
- return row.Limit ? (
154
- <Label
155
- theme={getUsageSeverityForStorageGroup(row.Usage)}
156
- className={b('usage-label', {overload: row.Usage >= 90})}
157
- >
158
- {row.Usage}%
159
- </Label>
160
- ) : (
161
- '-'
162
- );
163
- },
164
- // without a limit exclude usage from sort to display at the bottom
165
- sortAccessor: (row) => (row.Limit ? getUsage(row) : null),
166
- align: DataTable.LEFT,
167
- },
168
- {
169
- name: TableColumnsIds.GroupId,
170
- header: tableColumnsNames[TableColumnsIds.GroupId],
171
- width: 130,
172
- render: ({row}) => {
173
- return <span className={b('group-id')}>{row.GroupID}</span>;
174
- },
175
- sortAccessor: (row) => Number(row.GroupID),
176
- align: DataTable.RIGHT,
177
- },
178
- {
179
- name: TableColumnsIds.Used,
180
- header: tableColumnsNames[TableColumnsIds.Used],
181
- width: 100,
182
- render: ({row}) => {
183
- return bytesToGB(row.Used, true);
184
- },
185
- align: DataTable.RIGHT,
186
- },
187
- {
188
- name: TableColumnsIds.Limit,
189
- header: tableColumnsNames[TableColumnsIds.Limit],
190
- width: 100,
191
- render: ({row}) => {
192
- return bytesToGB(row.Limit);
193
- },
194
- align: DataTable.RIGHT,
195
- },
196
- {
197
- name: TableColumnsIds.UsedSpaceFlag,
198
- header: tableColumnsNames[TableColumnsIds.UsedSpaceFlag],
199
- width: 110,
200
- render: ({row}) => {
201
- const value = row.UsedSpaceFlag;
202
-
203
- let color = 'Red';
204
-
205
- if (value < 100) {
206
- color = 'Green';
207
- } else if (value < 10000) {
208
- color = 'Yellow';
209
- } else if (value < 1000000) {
210
- color = 'Orange';
211
- }
212
- return <EntityStatus status={color} />;
213
- },
214
- align: DataTable.CENTER,
215
- },
216
-
217
- {
218
- name: TableColumnsIds.Read,
219
- header: tableColumnsNames[TableColumnsIds.Read],
220
- width: 100,
221
- render: ({row}) => {
222
- return row.Read ? bytesToSpeed(row.Read) : '-';
223
- },
224
- align: DataTable.RIGHT,
225
- },
226
- {
227
- name: TableColumnsIds.Write,
228
- header: tableColumnsNames[TableColumnsIds.Write],
229
- width: 100,
230
- render: ({row}) => {
231
- return row.Write ? bytesToSpeed(row.Write) : '-';
232
- },
233
- align: DataTable.RIGHT,
234
- },
235
- {
236
- name: TableColumnsIds.VDisks,
237
- className: b('vdisks-column'),
238
- header: tableColumnsNames[TableColumnsIds.VDisks],
239
- render: ({row}) => (
240
- <div className={b('vdisks-wrapper')}>
241
- {row.VDisks?.map((vDisk) => {
242
- const donors = vDisk.Donors;
243
-
244
- return donors && donors.length > 0 ? (
245
- <Stack
246
- className={b('vdisks-item')}
247
- key={stringifyVdiskId(vDisk.VDiskId)}
248
- >
249
- <VDisk data={vDisk} nodes={nodes} />
250
- {donors.map((donor) => {
251
- const isFullData = isFullVDiskData(donor);
252
-
253
- return (
254
- <VDisk
255
- data={isFullData ? donor : {...donor, DonorMode: true}}
256
- // donor and acceptor are always in the same group
257
- nodes={nodes}
258
- key={stringifyVdiskId(
259
- isFullData ? donor.VDiskId : donor,
260
- )}
261
- />
262
- );
263
- })}
264
- </Stack>
265
- ) : (
266
- <div className={b('vdisks-item')} key={stringifyVdiskId(vDisk.VDiskId)}>
267
- <VDisk data={vDisk} nodes={nodes} />
268
- </div>
269
- );
270
- })}
271
- </div>
272
- ),
273
- align: DataTable.CENTER,
274
- sortable: false,
275
- width: 900,
276
- },
277
- ];
48
+ const rawColumns: Column<PreparedStorageGroup>[] = getStorageGroupsColumns(nodes);
278
49
 
279
50
  let columns = rawColumns.map((column) => ({
280
51
  ...column,
@@ -0,0 +1,278 @@
1
+ import cn from 'bem-cn-lite';
2
+
3
+ import DataTable, {type Column} from '@gravity-ui/react-data-table';
4
+ import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
5
+
6
+ import shieldIcon from '../../../assets/icons/shield.svg';
7
+ import type {ValueOf} from '../../../types/common';
8
+ import type {NodesMap} from '../../../types/store/nodesList';
9
+ import type {PreparedStorageGroup} from '../../../store/reducers/storage/types';
10
+ import {getUsage, isFullVDiskData} from '../../../utils/storage';
11
+ import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
12
+ import {stringifyVdiskId} from '../../../utils/dataFormatters/dataFormatters';
13
+ import EntityStatus from '../../../components/EntityStatus/EntityStatus';
14
+ import {Stack} from '../../../components/Stack/Stack';
15
+ import {VDisk} from '../VDisk';
16
+ import {getDegradedSeverity, getUsageSeverityForStorageGroup} from '../utils';
17
+ import i18n from './i18n';
18
+ import './StorageGroups.scss';
19
+
20
+ const b = cn('global-storage-groups');
21
+
22
+ const GROUPS_COLUMNS_IDS = {
23
+ PoolName: 'PoolName',
24
+ Kind: 'Kind',
25
+ Erasure: 'Erasure',
26
+ GroupId: 'GroupId',
27
+ Used: 'Used',
28
+ Limit: 'Limit',
29
+ Usage: 'Usage',
30
+ UsedSpaceFlag: 'UsedSpaceFlag',
31
+ Read: 'Read',
32
+ Write: 'Write',
33
+ VDisks: 'VDisks',
34
+ Degraded: 'Degraded',
35
+ } as const;
36
+
37
+ type GroupsColumns = ValueOf<typeof GROUPS_COLUMNS_IDS>;
38
+
39
+ const tableColumnsNames: Record<GroupsColumns, string> = {
40
+ PoolName: 'Pool Name',
41
+ Kind: 'Type',
42
+ Erasure: 'Erasure',
43
+ GroupId: 'Group ID',
44
+ Used: 'Used',
45
+ Limit: 'Limit',
46
+ UsedSpaceFlag: 'Space',
47
+ Usage: 'Usage',
48
+ Read: 'Read',
49
+ Write: 'Write',
50
+ VDisks: 'VDisks',
51
+ Degraded: 'Degraded',
52
+ };
53
+
54
+ const poolNameColumn: Column<PreparedStorageGroup> = {
55
+ name: GROUPS_COLUMNS_IDS.PoolName,
56
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.PoolName],
57
+ width: 250,
58
+ render: ({row}) => {
59
+ const splitted = row.PoolName?.split('/');
60
+ return (
61
+ <div className={b('pool-name-wrapper')}>
62
+ {splitted && (
63
+ <Popover
64
+ content={row.PoolName}
65
+ placement={['right']}
66
+ behavior={PopoverBehavior.Immediate}
67
+ >
68
+ <span className={b('pool-name')}>{splitted[splitted.length - 1]}</span>
69
+ </Popover>
70
+ )}
71
+ </div>
72
+ );
73
+ },
74
+ align: DataTable.LEFT,
75
+ };
76
+
77
+ const kindColumn: Column<PreparedStorageGroup> = {
78
+ name: GROUPS_COLUMNS_IDS.Kind,
79
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Kind],
80
+ // prettier-ignore
81
+ render: ({row}) => (
82
+ <>
83
+ <Label>{row.Kind || '—'}</Label>
84
+ {' '}
85
+ {row.Encryption && (
86
+ <Popover
87
+ content={i18n('encrypted')}
88
+ placement="right"
89
+ behavior={PopoverBehavior.Immediate}
90
+ >
91
+ <Label>
92
+ <Icon data={shieldIcon} />
93
+ </Label>
94
+ </Popover>
95
+ )}
96
+ </>
97
+ ),
98
+ sortable: false,
99
+ };
100
+
101
+ const erasureColumn: Column<PreparedStorageGroup> = {
102
+ name: GROUPS_COLUMNS_IDS.Erasure,
103
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Erasure],
104
+ render: ({row}) => (row.ErasureSpecies ? row.ErasureSpecies : '-'),
105
+ align: DataTable.LEFT,
106
+ sortable: false,
107
+ };
108
+
109
+ const degradedColumn: Column<PreparedStorageGroup> = {
110
+ name: GROUPS_COLUMNS_IDS.Degraded,
111
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Degraded],
112
+ width: 100,
113
+ render: ({row}) =>
114
+ row.Degraded ? (
115
+ <Label theme={getDegradedSeverity(row)}>Degraded: {row.Degraded}</Label>
116
+ ) : (
117
+ '-'
118
+ ),
119
+ align: DataTable.LEFT,
120
+ defaultOrder: DataTable.DESCENDING,
121
+ };
122
+
123
+ const usageColumn: Column<PreparedStorageGroup> = {
124
+ name: GROUPS_COLUMNS_IDS.Usage,
125
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Usage],
126
+ width: 100,
127
+ render: ({row}) => {
128
+ // without a limit the usage can be evaluated as 0,
129
+ // but the absence of a value is more clear
130
+ return row.Limit ? (
131
+ <Label
132
+ theme={getUsageSeverityForStorageGroup(row.Usage)}
133
+ className={b('usage-label', {overload: row.Usage >= 90})}
134
+ >
135
+ {row.Usage}%
136
+ </Label>
137
+ ) : (
138
+ '-'
139
+ );
140
+ },
141
+ // without a limit exclude usage from sort to display at the bottom
142
+ sortAccessor: (row) => (row.Limit ? getUsage(row) : null),
143
+ align: DataTable.LEFT,
144
+ sortable: false,
145
+ };
146
+
147
+ const groupIdColumn: Column<PreparedStorageGroup> = {
148
+ name: GROUPS_COLUMNS_IDS.GroupId,
149
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.GroupId],
150
+ width: 130,
151
+ render: ({row}) => {
152
+ return <span className={b('group-id')}>{row.GroupID}</span>;
153
+ },
154
+ sortAccessor: (row) => Number(row.GroupID),
155
+ align: DataTable.RIGHT,
156
+ sortable: false,
157
+ };
158
+
159
+ const usedColumn: Column<PreparedStorageGroup> = {
160
+ name: GROUPS_COLUMNS_IDS.Used,
161
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Used],
162
+ width: 100,
163
+ render: ({row}) => {
164
+ return bytesToGB(row.Used, true);
165
+ },
166
+ align: DataTable.RIGHT,
167
+ sortable: false,
168
+ };
169
+
170
+ const limitColumn: Column<PreparedStorageGroup> = {
171
+ name: GROUPS_COLUMNS_IDS.Limit,
172
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Limit],
173
+ width: 100,
174
+ render: ({row}) => {
175
+ return bytesToGB(row.Limit);
176
+ },
177
+ align: DataTable.RIGHT,
178
+ sortable: false,
179
+ };
180
+
181
+ const usedSpaceFlagColumn: Column<PreparedStorageGroup> = {
182
+ name: GROUPS_COLUMNS_IDS.UsedSpaceFlag,
183
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.UsedSpaceFlag],
184
+ width: 110,
185
+ render: ({row}) => {
186
+ const value = row.UsedSpaceFlag;
187
+
188
+ let color = 'Red';
189
+
190
+ if (value < 100) {
191
+ color = 'Green';
192
+ } else if (value < 10000) {
193
+ color = 'Yellow';
194
+ } else if (value < 1000000) {
195
+ color = 'Orange';
196
+ }
197
+ return <EntityStatus status={color} />;
198
+ },
199
+ align: DataTable.CENTER,
200
+ };
201
+
202
+ const readColumn: Column<PreparedStorageGroup> = {
203
+ name: GROUPS_COLUMNS_IDS.Read,
204
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Read],
205
+ width: 100,
206
+ render: ({row}) => {
207
+ return row.Read ? bytesToSpeed(row.Read) : '-';
208
+ },
209
+ align: DataTable.RIGHT,
210
+ };
211
+
212
+ const writeColumn: Column<PreparedStorageGroup> = {
213
+ name: GROUPS_COLUMNS_IDS.Write,
214
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.Write],
215
+ width: 100,
216
+ render: ({row}) => {
217
+ return row.Write ? bytesToSpeed(row.Write) : '-';
218
+ },
219
+ align: DataTable.RIGHT,
220
+ };
221
+
222
+ const getVdiscksColumn = (nodes?: NodesMap): Column<PreparedStorageGroup> => ({
223
+ name: GROUPS_COLUMNS_IDS.VDisks,
224
+ className: b('vdisks-column'),
225
+ header: tableColumnsNames[GROUPS_COLUMNS_IDS.VDisks],
226
+ render: ({row}) => (
227
+ <div className={b('vdisks-wrapper')}>
228
+ {row.VDisks?.map((vDisk) => {
229
+ const donors = vDisk.Donors;
230
+
231
+ return donors && donors.length > 0 ? (
232
+ <Stack className={b('vdisks-item')} key={stringifyVdiskId(vDisk.VDiskId)}>
233
+ <VDisk data={vDisk} nodes={nodes} />
234
+ {donors.map((donor) => {
235
+ const isFullData = isFullVDiskData(donor);
236
+
237
+ return (
238
+ <VDisk
239
+ data={isFullData ? donor : {...donor, DonorMode: true}}
240
+ // donor and acceptor are always in the same group
241
+ nodes={nodes}
242
+ key={stringifyVdiskId(isFullData ? donor.VDiskId : donor)}
243
+ />
244
+ );
245
+ })}
246
+ </Stack>
247
+ ) : (
248
+ <div className={b('vdisks-item')} key={stringifyVdiskId(vDisk.VDiskId)}>
249
+ <VDisk data={vDisk} nodes={nodes} />
250
+ </div>
251
+ );
252
+ })}
253
+ </div>
254
+ ),
255
+ align: DataTable.CENTER,
256
+ width: 900,
257
+ });
258
+
259
+ export const getStorageTopGroupsColumns = (): Column<PreparedStorageGroup>[] => {
260
+ return [groupIdColumn, kindColumn, erasureColumn, usageColumn, usedColumn, limitColumn];
261
+ };
262
+
263
+ export const getStorageGroupsColumns = (nodes?: NodesMap): Column<PreparedStorageGroup>[] => {
264
+ return [
265
+ poolNameColumn,
266
+ kindColumn,
267
+ erasureColumn,
268
+ degradedColumn,
269
+ usageColumn,
270
+ groupIdColumn,
271
+ usedColumn,
272
+ limitColumn,
273
+ usedSpaceFlagColumn,
274
+ readColumn,
275
+ writeColumn,
276
+ getVdiscksColumn(nodes),
277
+ ];
278
+ };