ydb-embedded-ui 4.18.0 → 4.19.1
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.
- package/CHANGELOG.md +22 -0
- package/dist/components/FullNodeViewer/FullNodeViewer.scss +1 -1
- package/dist/components/FullNodeViewer/FullNodeViewer.tsx +13 -4
- package/dist/components/ProgressViewer/ProgressViewer.tsx +11 -5
- package/dist/containers/Nodes/getNodesColumns.tsx +1 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +1 -1
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -232
- package/dist/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx +278 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +22 -6
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.scss +41 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TenantStorage.tsx +68 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +76 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +105 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +2 -0
- package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +2 -0
- package/dist/containers/Versions/NodesTable/NodesTable.tsx +1 -0
- package/dist/store/reducers/index.ts +4 -0
- package/dist/store/reducers/storage/utils.ts +20 -11
- package/dist/store/reducers/tenantOverview/executeTopTables/executeTopTables.ts +93 -0
- package/dist/store/reducers/tenantOverview/executeTopTables/types.ts +14 -0
- package/dist/store/reducers/tenantOverview/topStorageGroups/topStorageGroups.ts +98 -0
- package/dist/store/reducers/tenantOverview/topStorageGroups/types.ts +29 -0
- package/dist/store/reducers/tenantOverview/topStorageGroups/utils.ts +20 -0
- package/dist/store/reducers/tenants/utils.ts +28 -18
- package/dist/styles/constants.scss +4 -0
- package/dist/types/additionalProps.ts +1 -1
- package/dist/types/api/storage.ts +1 -1
- package/dist/types/api/tenant.ts +21 -8
- package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +10 -1
- package/dist/utils/bytesParsers/formatBytes.ts +3 -3
- package/dist/utils/constants.ts +8 -0
- package/dist/utils/dataFormatters/__test__/roundToSignificant.test.ts +22 -0
- package/dist/utils/dataFormatters/dataFormatters.ts +46 -20
- 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
|
|
@@ -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:
|
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
|
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
|
-
|
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
|
}
|
@@ -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 {
|
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 {
|
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
|
+
};
|