ydb-embedded-ui 1.8.6 → 1.9.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.
- package/CHANGELOG.md +41 -0
- package/dist/components/BasicNodeViewer/BasicNodeViewer.scss +43 -0
- package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +53 -0
- package/dist/components/BasicNodeViewer/index.ts +1 -0
- package/dist/components/EntityStatus/EntityStatus.js +15 -3
- package/dist/components/FullNodeViewer/FullNodeViewer.js +29 -48
- package/dist/components/FullNodeViewer/FullNodeViewer.scss +0 -45
- package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +52 -0
- package/dist/components/InfoViewer/index.ts +4 -0
- package/dist/components/InfoViewer/utils.ts +32 -0
- package/dist/components/ProgressViewer/ProgressViewer.js +1 -1
- package/dist/components/TabletsOverall/TabletsOverall.tsx +1 -1
- package/dist/containers/App/App.js +3 -5
- package/dist/containers/Node/Node.scss +5 -1
- package/dist/containers/Node/Node.tsx +7 -1
- package/dist/containers/Node/NodeOverview/NodeOverview.tsx +1 -3
- package/dist/containers/Node/NodeStructure/NodeStructure.scss +30 -1
- package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +25 -0
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +24 -2
- package/dist/containers/Nodes/Nodes.js +1 -0
- package/dist/containers/ReduxTooltip/ReduxTooltip.js +1 -1
- package/dist/containers/Tablets/Tablets.js +1 -1
- package/dist/containers/Tablets/Tablets.scss +0 -6
- package/dist/containers/TabletsFilters/TabletsFilters.scss +0 -7
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +6 -2
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.js +6 -2
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +6 -2
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +16 -2
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +15 -13
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +22 -6
- package/dist/containers/Tenant/Preview/Preview.js +3 -0
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +20 -16
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -0
- package/dist/types/api/schema.ts +36 -0
- package/dist/types/api/storage.ts +54 -0
- package/dist/utils/getNodesColumns.js +2 -0
- package/dist/utils/pdisk.ts +74 -0
- package/dist/utils/tooltip.js +27 -0
- package/dist/utils/utils.js +8 -1
- package/package.json +2 -2
@@ -14,9 +14,11 @@ import {Vdisk} from './Vdisk';
|
|
14
14
|
|
15
15
|
import {bytesToGB, pad9} from '../../../utils/utils';
|
16
16
|
import {formatStorageValuesToGb} from '../../../utils';
|
17
|
+
import {getPDiskType} from '../../../utils/pdisk';
|
17
18
|
|
18
19
|
import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
|
19
20
|
import {valueIsDefined} from './NodeStructure';
|
21
|
+
import {PDiskTitleBadge} from './PDiskTitleBadge';
|
20
22
|
|
21
23
|
const b = cn('kv-node-structure');
|
22
24
|
|
@@ -230,6 +232,7 @@ export function PDisk(props: PDiskProps) {
|
|
230
232
|
}
|
231
233
|
if (valueIsDefined(Category)) {
|
232
234
|
pdiskInfo.push({label: 'Category', value: Category});
|
235
|
+
pdiskInfo.push({label: 'Type', value: getPDiskType(data)});
|
233
236
|
}
|
234
237
|
pdiskInfo.push({
|
235
238
|
label: 'Allocated Size',
|
@@ -286,8 +289,27 @@ export function PDisk(props: PDiskProps) {
|
|
286
289
|
<div className={b('pdisk')} id={props.id}>
|
287
290
|
<div className={b('pdisk-header')}>
|
288
291
|
<div className={b('pdisk-title-wrapper')}>
|
289
|
-
<
|
290
|
-
<
|
292
|
+
<EntityStatus status={data.Device} />
|
293
|
+
<PDiskTitleBadge
|
294
|
+
label="PDiskID"
|
295
|
+
value={data.PDiskId}
|
296
|
+
className={b('pdisk-title-id')}
|
297
|
+
/>
|
298
|
+
<PDiskTitleBadge
|
299
|
+
value={getPDiskType(data)}
|
300
|
+
className={b('pdisk-title-type')}
|
301
|
+
/>
|
302
|
+
<ProgressViewer
|
303
|
+
value={data.TotalSize - data.AvailableSize}
|
304
|
+
capacity={data.TotalSize}
|
305
|
+
formatValues={formatStorageValuesToGb}
|
306
|
+
colorizeProgress={true}
|
307
|
+
className={b('pdisk-title-size')}
|
308
|
+
/>
|
309
|
+
<PDiskTitleBadge
|
310
|
+
label="VDisks"
|
311
|
+
value={data.vDisks.length}
|
312
|
+
/>
|
291
313
|
</div>
|
292
314
|
<Button onClick={unfolded ? onClosePDiskDetails : onOpenPDiskDetails} view="flat-secondary">
|
293
315
|
<ArrowToggle direction={unfolded ? 'top' : 'bottom'} />
|
@@ -150,7 +150,7 @@ class Tablets extends React.Component {
|
|
150
150
|
const {stateFilter, typeFilter, className} = this.props;
|
151
151
|
|
152
152
|
return (
|
153
|
-
<div className={
|
153
|
+
<div className={b(null, className)}>
|
154
154
|
<div className={b('header')}>
|
155
155
|
<Select
|
156
156
|
className={b('filter-control')}
|
@@ -3,7 +3,6 @@
|
|
3
3
|
.tablets-filters {
|
4
4
|
overflow: auto;
|
5
5
|
|
6
|
-
max-height: 400px;
|
7
6
|
@include flex-container();
|
8
7
|
|
9
8
|
&__node {
|
@@ -19,18 +18,12 @@
|
|
19
18
|
}
|
20
19
|
|
21
20
|
&__items {
|
22
|
-
display: flex;
|
23
21
|
overflow: auto;
|
24
22
|
flex: 1 1 auto;
|
25
|
-
flex-wrap: wrap;
|
26
23
|
|
27
24
|
padding: 5px 20px;
|
28
25
|
}
|
29
26
|
|
30
|
-
&__items-wrapper {
|
31
|
-
overflow: auto;
|
32
|
-
}
|
33
|
-
|
34
27
|
&__filters {
|
35
28
|
display: flex;
|
36
29
|
align-items: center;
|
@@ -1,9 +1,13 @@
|
|
1
|
+
$section-title-margin: 20px;
|
2
|
+
$section-title-line-height: 24px;
|
3
|
+
|
1
4
|
.kv-detailed-overview {
|
2
5
|
display: flex;
|
3
|
-
gap:
|
6
|
+
gap: 20px;
|
4
7
|
&__section {
|
5
8
|
display: flex;
|
6
|
-
|
9
|
+
overflow-x: hidden;
|
10
|
+
flex: 0 0 calc(50% - 10px);
|
7
11
|
flex-direction: column;
|
8
12
|
}
|
9
13
|
|
@@ -97,10 +97,14 @@ class Healthcheck extends React.Component {
|
|
97
97
|
</div>
|
98
98
|
{this.renderUpdateButton()}
|
99
99
|
</div>
|
100
|
-
<div>
|
100
|
+
<div className={b('preview-content')}>
|
101
101
|
{text}
|
102
102
|
{!statusOk && (
|
103
|
-
<Button
|
103
|
+
<Button
|
104
|
+
view="flat-info"
|
105
|
+
onClick={showMoreHandler}
|
106
|
+
size="s"
|
107
|
+
>
|
104
108
|
Show details
|
105
109
|
</Button>
|
106
110
|
)}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
@use '../DetailedOverview/DetailedOverview.scss' as detailedOverview;
|
1
2
|
@import '../../../../styles/mixins.scss';
|
2
3
|
|
3
4
|
.healthcheck {
|
@@ -37,14 +38,17 @@
|
|
37
38
|
|
38
39
|
&__status-wrapper {
|
39
40
|
display: flex;
|
40
|
-
align-items: baseline;
|
41
41
|
|
42
|
-
margin-bottom:
|
42
|
+
margin-bottom: detailedOverview.$section-title-margin;
|
43
43
|
gap: 8px;
|
44
44
|
}
|
45
45
|
|
46
46
|
&__preview-title {
|
47
47
|
font-weight: 600;
|
48
|
+
line-height: detailedOverview.$section-title-line-height;
|
49
|
+
}
|
50
|
+
|
51
|
+
&__preview-content {
|
48
52
|
line-height: 24px;
|
49
53
|
}
|
50
54
|
|
@@ -6,9 +6,10 @@ import {Loader} from '@yandex-cloud/uikit';
|
|
6
6
|
|
7
7
|
//@ts-ignore
|
8
8
|
import SchemaInfoViewer from '../../Schema/SchemaInfoViewer/SchemaInfoViewer';
|
9
|
+
import {IndexInfoViewer} from '../../../../components/IndexInfoViewer/IndexInfoViewer';
|
9
10
|
|
10
11
|
import type {EPathType} from '../../../../types/api/schema';
|
11
|
-
import {isColumnEntityType, isTableType} from '../../utils/schema';
|
12
|
+
import {isColumnEntityType, isTableType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
|
12
13
|
import {AutoFetcher} from '../../../../utils/autofetcher';
|
13
14
|
//@ts-ignore
|
14
15
|
import {getSchema} from '../../../../store/reducers/schema';
|
@@ -112,11 +113,24 @@ function Overview(props: OverviewProps) {
|
|
112
113
|
);
|
113
114
|
};
|
114
115
|
|
116
|
+
const renderContent = () => {
|
117
|
+
switch (mapPathTypeToNavigationTreeType(props.type)) {
|
118
|
+
case 'index':
|
119
|
+
return (
|
120
|
+
<IndexInfoViewer data={schemaData} />
|
121
|
+
);
|
122
|
+
default:
|
123
|
+
return (
|
124
|
+
<SchemaInfoViewer fullPath={currentItem.Path} data={schemaData} />
|
125
|
+
);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
115
129
|
return loading && !wasLoaded ? (
|
116
130
|
renderLoader()
|
117
131
|
) : (
|
118
132
|
<div className={props.className}>
|
119
|
-
|
133
|
+
{renderContent()}
|
120
134
|
</div>
|
121
135
|
);
|
122
136
|
}
|
@@ -25,9 +25,11 @@ const renderName = (tenant) => {
|
|
25
25
|
if (tenant) {
|
26
26
|
const {Name} = tenant;
|
27
27
|
return (
|
28
|
-
<div className={b('tenant-name')}>
|
28
|
+
<div className={b('tenant-name-wrapper')}>
|
29
29
|
<EntityStatus status={tenant.State} />
|
30
|
-
<span
|
30
|
+
<span className={b('tenant-name-trim')}>
|
31
|
+
<span className={b('tenant-name')}>{Name}</span>
|
32
|
+
</span>
|
31
33
|
</div>
|
32
34
|
);
|
33
35
|
}
|
@@ -139,17 +141,17 @@ class TenantOverview extends React.Component {
|
|
139
141
|
this.props.tenant.Name,
|
140
142
|
this.props.tenant.Type,
|
141
143
|
)}
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
144
|
+
</div>
|
145
|
+
<div className={b('system-tablets')}>
|
146
|
+
{SystemTablets &&
|
147
|
+
SystemTablets.map((tablet, tabletIndex) => (
|
148
|
+
<Tablet
|
149
|
+
onMouseEnter={showTooltip}
|
150
|
+
onMouseLeave={hideTooltip}
|
151
|
+
key={tabletIndex}
|
152
|
+
tablet={tablet}
|
153
|
+
/>
|
154
|
+
))}
|
153
155
|
</div>
|
154
156
|
<div className={b('common-info')}>
|
155
157
|
{PoolStats ? (
|
@@ -1,30 +1,46 @@
|
|
1
|
+
@use '../DetailedOverview/DetailedOverview.scss' as detailedOverview;
|
2
|
+
|
1
3
|
.tenant-overview {
|
2
4
|
padding-bottom: 20px;
|
3
5
|
&__loader {
|
4
6
|
display: flex;
|
5
7
|
justify-content: center;
|
6
8
|
}
|
7
|
-
&__tenant-name {
|
9
|
+
&__tenant-name-wrapper {
|
8
10
|
display: flex;
|
11
|
+
overflow: hidden;
|
9
12
|
align-items: center;
|
10
13
|
|
11
14
|
& .yc-link {
|
12
15
|
display: flex;
|
13
16
|
}
|
14
17
|
}
|
18
|
+
&__tenant-name-trim {
|
19
|
+
overflow: hidden;
|
20
|
+
|
21
|
+
white-space: nowrap;
|
22
|
+
text-overflow: ellipsis;
|
23
|
+
direction: rtl;
|
24
|
+
}
|
25
|
+
|
26
|
+
&__tenant-name {
|
27
|
+
unicode-bidi: plaintext;
|
28
|
+
}
|
15
29
|
|
16
30
|
&__top {
|
17
31
|
display: flex;
|
18
32
|
align-items: center;
|
19
33
|
|
20
|
-
margin-bottom:
|
34
|
+
margin-bottom: 10px;
|
35
|
+
|
36
|
+
line-height: 24px;
|
21
37
|
}
|
22
38
|
|
23
39
|
&__top-label {
|
24
|
-
margin-bottom:
|
40
|
+
margin-bottom: detailedOverview.$section-title-margin;
|
25
41
|
|
26
42
|
font-weight: 600;
|
27
|
-
line-height:
|
43
|
+
line-height: detailedOverview.$section-title-line-height;
|
28
44
|
gap: 10px;
|
29
45
|
}
|
30
46
|
|
@@ -42,7 +58,7 @@
|
|
42
58
|
flex-wrap: wrap;
|
43
59
|
align-items: center;
|
44
60
|
|
45
|
-
margin-
|
61
|
+
margin-bottom: 35px;
|
46
62
|
}
|
47
63
|
|
48
64
|
&__collapse-title {
|
@@ -76,7 +92,7 @@
|
|
76
92
|
margin-bottom: 20px;
|
77
93
|
|
78
94
|
font-size: var(--yc-text-body-2-font-size);
|
79
|
-
font-weight:
|
95
|
+
font-weight: 600;
|
80
96
|
line-height: var(--yc-text-body-2-line-height);
|
81
97
|
}
|
82
98
|
}
|
@@ -12,6 +12,7 @@ import Fullscreen from '../../../components/Fullscreen/Fullscreen';
|
|
12
12
|
import {sendQuery, setQueryOptions} from '../../../store/reducers/preview';
|
13
13
|
import {showTooltip, hideTooltip} from '../../../store/reducers/tooltip';
|
14
14
|
import {prepareQueryError, prepareQueryResponse} from '../../../utils/index';
|
15
|
+
import {isNumeric} from '../../../utils/utils';
|
15
16
|
|
16
17
|
import {isTableType} from '../utils/schema';
|
17
18
|
import {AutoFetcher} from '../../../utils/autofetcher';
|
@@ -127,6 +128,8 @@ class Preview extends React.Component {
|
|
127
128
|
if (data && data.length > 0) {
|
128
129
|
columns = Object.keys(data[0]).map((key) => ({
|
129
130
|
name: key,
|
131
|
+
align: isNumeric(data[0][key]) ? DataTable.RIGHT : DataTable.LEFT,
|
132
|
+
sortAccessor: (row) => isNumeric(row[key]) ? Number(row[key]) : row[key],
|
130
133
|
render: ({value}) => {
|
131
134
|
return (
|
132
135
|
<span
|
@@ -28,29 +28,33 @@ class SchemaInfoViewer extends React.Component {
|
|
28
28
|
if (data) {
|
29
29
|
const {PathDescription = {}} = data;
|
30
30
|
const {TableStats = {}, TabletMetrics = {}} = PathDescription;
|
31
|
-
const
|
32
|
-
TableStats &&
|
33
|
-
Object.keys(TableStats).map((key) => ({
|
34
|
-
label: key,
|
35
|
-
value: TableStats[key].toString(),
|
36
|
-
}));
|
31
|
+
const {PartCount, ...restTableStats} = TableStats;
|
37
32
|
|
38
|
-
const
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
|
43
|
-
}));
|
33
|
+
const priorityInfo = [{
|
34
|
+
label: 'PartCount',
|
35
|
+
value: PartCount,
|
36
|
+
}].filter(({value}) => value !== undefined);
|
44
37
|
|
45
|
-
|
46
|
-
|
38
|
+
const tableStatsInfo = Object.keys(restTableStats).map((key) => ({
|
39
|
+
label: key,
|
40
|
+
value: TableStats[key].toString(),
|
41
|
+
}));
|
47
42
|
|
48
|
-
const
|
43
|
+
const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) => ({
|
44
|
+
label: key,
|
45
|
+
value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
|
46
|
+
}));
|
49
47
|
|
48
|
+
const generalInfo = [
|
49
|
+
...priorityInfo,
|
50
|
+
...tabletMetricsInfo,
|
51
|
+
...tableStatsInfo,
|
52
|
+
];
|
53
|
+
|
50
54
|
return (
|
51
55
|
<div className={b()}>
|
52
56
|
<div className={b('item')}>
|
53
|
-
{
|
57
|
+
{generalInfo.length ? (
|
54
58
|
<InfoViewer info={generalInfo}></InfoViewer>
|
55
59
|
) : (
|
56
60
|
<div>Empty</div>
|
package/dist/types/api/schema.ts
CHANGED
@@ -54,6 +54,8 @@ interface TPathDescription {
|
|
54
54
|
|
55
55
|
ColumnStoreDescription?: unknown;
|
56
56
|
ColumnTableDescription?: unknown;
|
57
|
+
|
58
|
+
TableIndex?: TIndexDescription;
|
57
59
|
}
|
58
60
|
|
59
61
|
interface TDirEntry {
|
@@ -80,6 +82,27 @@ interface TDirEntry {
|
|
80
82
|
Version?: TPathVersion;
|
81
83
|
}
|
82
84
|
|
85
|
+
export interface TIndexDescription {
|
86
|
+
Name?: string;
|
87
|
+
/** uint64 */
|
88
|
+
LocalPathId?: string;
|
89
|
+
|
90
|
+
Type?: EIndexType;
|
91
|
+
State?: EIndexState;
|
92
|
+
|
93
|
+
KeyColumnNames?: string[];
|
94
|
+
|
95
|
+
/** uint64 */
|
96
|
+
SchemaVersion?: string;
|
97
|
+
|
98
|
+
/** uint64 */
|
99
|
+
PathOwnerId?: string;
|
100
|
+
|
101
|
+
DataColumnNames?: string[];
|
102
|
+
/** uint64 */
|
103
|
+
DataSize?: string;
|
104
|
+
}
|
105
|
+
|
83
106
|
// incomplete
|
84
107
|
export enum EPathType {
|
85
108
|
EPathTypeInvalid = 'EPathTypeInvalid',
|
@@ -112,6 +135,19 @@ enum EPathState {
|
|
112
135
|
EPathStateMoving = 'EPathStateMoving',
|
113
136
|
}
|
114
137
|
|
138
|
+
enum EIndexType {
|
139
|
+
EIndexTypeInvalid = 'EIndexTypeInvalid',
|
140
|
+
EIndexTypeGlobal = 'EIndexTypeGlobal',
|
141
|
+
EIndexTypeGlobalAsync = 'EIndexTypeGlobalAsync',
|
142
|
+
}
|
143
|
+
|
144
|
+
enum EIndexState {
|
145
|
+
EIndexStateInvalid = 'EIndexStateInvalid',
|
146
|
+
EIndexStateReady = 'EIndexStateReady',
|
147
|
+
EIndexStateNotReady = 'EIndexStateNotReady',
|
148
|
+
EIndexStateWriteOnly = 'EIndexStateWriteOnly',
|
149
|
+
}
|
150
|
+
|
115
151
|
// incomplete
|
116
152
|
interface TPathVersion {
|
117
153
|
/** uint64 */
|
@@ -0,0 +1,54 @@
|
|
1
|
+
enum EFlag {
|
2
|
+
Grey = 'Grey',
|
3
|
+
Green = 'Green',
|
4
|
+
Yellow = 'Yellow',
|
5
|
+
Orange = 'Orange',
|
6
|
+
Red = 'Red',
|
7
|
+
}
|
8
|
+
|
9
|
+
enum TPDiskState {
|
10
|
+
Initial = 'Initial',
|
11
|
+
InitialFormatRead = 'InitialFormatRead',
|
12
|
+
InitialFormatReadError = 'InitialFormatReadError',
|
13
|
+
InitialSysLogRead = 'InitialSysLogRead',
|
14
|
+
InitialSysLogReadError = 'InitialSysLogReadError',
|
15
|
+
InitialSysLogParseError = 'InitialSysLogParseError',
|
16
|
+
InitialCommonLogRead = 'InitialCommonLogRead',
|
17
|
+
InitialCommonLogReadError = 'InitialCommonLogReadError',
|
18
|
+
InitialCommonLogParseError = 'InitialCommonLogParseError',
|
19
|
+
CommonLoggerInitError = 'CommonLoggerInitError',
|
20
|
+
Normal = 'Normal',
|
21
|
+
OpenFileError = 'OpenFileError',
|
22
|
+
ChunkQuotaError = 'ChunkQuotaError',
|
23
|
+
DeviceIoError = 'DeviceIoError',
|
24
|
+
|
25
|
+
Missing = 'Missing',
|
26
|
+
Timeout = 'Timeout',
|
27
|
+
NodeDisconnected = 'NodeDisconnected',
|
28
|
+
Unknown = 'Unknown',
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface TPDiskStateInfo {
|
32
|
+
PDiskId?: number;
|
33
|
+
/** uint64 */
|
34
|
+
CreateTime?: string;
|
35
|
+
/** uint64 */
|
36
|
+
ChangeTime?: string;
|
37
|
+
Path?: string;
|
38
|
+
/** uint64 */
|
39
|
+
Guid?: string;
|
40
|
+
/** uint64 */
|
41
|
+
Category?: string;
|
42
|
+
/** uint64 */
|
43
|
+
AvailableSize?: string;
|
44
|
+
/** uint64 */
|
45
|
+
TotalSize?: string;
|
46
|
+
State?: TPDiskState;
|
47
|
+
NodeId?: number;
|
48
|
+
Count?: number;
|
49
|
+
Device?: EFlag;
|
50
|
+
Realtime?: EFlag;
|
51
|
+
StateFlag?: EFlag;
|
52
|
+
Overall?: EFlag;
|
53
|
+
SerialNumber?: string;
|
54
|
+
}
|
@@ -33,6 +33,8 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
33
33
|
<div className={b('host-name-wrapper')}>
|
34
34
|
<EntityStatus
|
35
35
|
name={row.Host}
|
36
|
+
onNameMouseEnter={(e) => showTooltip(e.target, row, 'nodeEndpoints')}
|
37
|
+
onNameMouseLeave={hideTooltip}
|
36
38
|
status={row.Overall}
|
37
39
|
path={getDefaultNodePath(row.NodeId)}
|
38
40
|
hasClipboardButton
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import type {TPDiskStateInfo} from "../types/api/storage";
|
2
|
+
|
3
|
+
// TODO: move to utils or index after converting them to TS
|
4
|
+
/**
|
5
|
+
* Parses a binary string containing a bit field into an object with binary values.
|
6
|
+
* This is an implementation based on string manipulation, since JS can only perform
|
7
|
+
* bitwise operations with 32-bits integers, and YDB sends uint64.
|
8
|
+
* @see https://en.cppreference.com/w/c/language/bit_field
|
9
|
+
* @param binaryString - binary string representing a bit field
|
10
|
+
* @param bitFieldStruct - bit field description, <field => size in bits>, in order starting from the rightmost bit
|
11
|
+
* @returns object with binary values
|
12
|
+
*/
|
13
|
+
export const parseBitField = <T extends Record<string, number>>(
|
14
|
+
binaryString: string,
|
15
|
+
bitFieldStruct: T,
|
16
|
+
): Record<keyof T, string> => {
|
17
|
+
const fields: Partial<Record<keyof T, string>> = {};
|
18
|
+
|
19
|
+
Object.entries(bitFieldStruct).reduce((prefixSize, [field, size]: [keyof T, number]) => {
|
20
|
+
const end = binaryString.length - prefixSize;
|
21
|
+
const start = end - size;
|
22
|
+
fields[field] = binaryString.substring(start, end) || '0';
|
23
|
+
|
24
|
+
return prefixSize + size;
|
25
|
+
}, 0);
|
26
|
+
|
27
|
+
return fields as Record<keyof T, string>;
|
28
|
+
};
|
29
|
+
|
30
|
+
export enum IPDiskType {
|
31
|
+
ROT = 'ROT',
|
32
|
+
SSD = 'SSD',
|
33
|
+
MVME = 'NVME',
|
34
|
+
}
|
35
|
+
|
36
|
+
// Bear with me.
|
37
|
+
// Disk type is determined by the field Category.
|
38
|
+
// Category is a bit field defined as follows:
|
39
|
+
// struct {
|
40
|
+
// ui64 IsSolidState : 1;
|
41
|
+
// ui64 Kind : 55;
|
42
|
+
// ui64 TypeExt : 8;
|
43
|
+
// }
|
44
|
+
// For compatibility TypeExt is not used for old types (ROT, SSD), so the following scheme is used:
|
45
|
+
// ROT -> IsSolidState# 0, TypeExt# 0
|
46
|
+
// SSD -> IsSolidState# 1, TypeExt# 0
|
47
|
+
// NVME -> IsSolidState# 1, TypeExt# 2
|
48
|
+
// Reference on bit fields: https://en.cppreference.com/w/c/language/bit_field
|
49
|
+
export const getPDiskType = (data: TPDiskStateInfo): IPDiskType | undefined => {
|
50
|
+
if (!data.Category) {
|
51
|
+
return undefined;
|
52
|
+
}
|
53
|
+
|
54
|
+
// Category is uint64, too big for Number or bitwise operators, thus BigInt and a custom parser
|
55
|
+
const categotyBin = BigInt(data.Category).toString(2);
|
56
|
+
const categoryBitField = parseBitField(categotyBin, {
|
57
|
+
isSolidState: 1,
|
58
|
+
kind: 55,
|
59
|
+
typeExt: 8,
|
60
|
+
});
|
61
|
+
|
62
|
+
if (categoryBitField.isSolidState === '1') {
|
63
|
+
switch (parseInt(categoryBitField.typeExt, 2)) {
|
64
|
+
case 0:
|
65
|
+
return IPDiskType.SSD;
|
66
|
+
case 2:
|
67
|
+
return IPDiskType.MVME;
|
68
|
+
}
|
69
|
+
} else if (categoryBitField.typeExt === '0') {
|
70
|
+
return IPDiskType.ROT;
|
71
|
+
}
|
72
|
+
|
73
|
+
return undefined;
|
74
|
+
};
|
package/dist/utils/tooltip.js
CHANGED
@@ -109,6 +109,32 @@ const NodeTooltip = (props) => {
|
|
109
109
|
);
|
110
110
|
};
|
111
111
|
|
112
|
+
const NodeEndpointsTooltip = (props) => {
|
113
|
+
const {data} = props;
|
114
|
+
return (
|
115
|
+
data && (
|
116
|
+
<div className={nodeB()}>
|
117
|
+
<table>
|
118
|
+
<tbody>
|
119
|
+
{data.Rack && (
|
120
|
+
<tr>
|
121
|
+
<td className={nodeB('label')}>Rack</td>
|
122
|
+
<td className={nodeB('value')}>{data.Rack}</td>
|
123
|
+
</tr>
|
124
|
+
)}
|
125
|
+
{data.Endpoints && data.Endpoints.length && data.Endpoints.map(({Name, Address}) => (
|
126
|
+
<tr key={Name}>
|
127
|
+
<td className={nodeB('label')}>{Name}</td>
|
128
|
+
<td className={nodeB('value')}>{Address}</td>
|
129
|
+
</tr>
|
130
|
+
))}
|
131
|
+
</tbody>
|
132
|
+
</table>
|
133
|
+
</div>
|
134
|
+
)
|
135
|
+
);
|
136
|
+
};
|
137
|
+
|
112
138
|
const tabletsOverallB = cn('tabletsOverall-tooltip');
|
113
139
|
|
114
140
|
const TabletsOverallTooltip = (props) => {
|
@@ -175,6 +201,7 @@ export const tooltipTemplates = {
|
|
175
201
|
tablet: (data, additionalData) => <TabletTooltip data={data} additionalData={additionalData} />,
|
176
202
|
// eslint-disable-next-line react/display-name
|
177
203
|
node: (data) => <NodeTooltip data={data} />,
|
204
|
+
nodeEndpoints: (data) => <NodeEndpointsTooltip data={data} />,
|
178
205
|
// eslint-disable-next-line react/display-name
|
179
206
|
tabletsOverall: (data) => <TabletsOverallTooltip data={data} />,
|
180
207
|
// eslint-disable-next-line react/display-name
|
package/dist/utils/utils.js
CHANGED
@@ -81,4 +81,11 @@ export function pad9(val) {
|
|
81
81
|
result = "0" + result;
|
82
82
|
}
|
83
83
|
return result;
|
84
|
-
}
|
84
|
+
}
|
85
|
+
|
86
|
+
export function isNumeric(value) {
|
87
|
+
// need both isNaN and isNaN(parseFloat):
|
88
|
+
// - isNaN treats true/false/''/etc. as numbers, parseFloat fixes this
|
89
|
+
// - parseFloat treats '123qwe' as number, isNaN fixes this
|
90
|
+
return !isNaN(value) && !isNaN(parseFloat(value));
|
91
|
+
};
|