ydb-embedded-ui 1.8.6 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
};
|