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.
Files changed (40) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.scss +43 -0
  3. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +53 -0
  4. package/dist/components/BasicNodeViewer/index.ts +1 -0
  5. package/dist/components/EntityStatus/EntityStatus.js +15 -3
  6. package/dist/components/FullNodeViewer/FullNodeViewer.js +29 -48
  7. package/dist/components/FullNodeViewer/FullNodeViewer.scss +0 -45
  8. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +52 -0
  9. package/dist/components/InfoViewer/index.ts +4 -0
  10. package/dist/components/InfoViewer/utils.ts +32 -0
  11. package/dist/components/ProgressViewer/ProgressViewer.js +1 -1
  12. package/dist/components/TabletsOverall/TabletsOverall.tsx +1 -1
  13. package/dist/containers/App/App.js +3 -5
  14. package/dist/containers/Node/Node.scss +5 -1
  15. package/dist/containers/Node/Node.tsx +7 -1
  16. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +1 -3
  17. package/dist/containers/Node/NodeStructure/NodeStructure.scss +30 -1
  18. package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +25 -0
  19. package/dist/containers/Node/NodeStructure/Pdisk.tsx +24 -2
  20. package/dist/containers/Nodes/Nodes.js +1 -0
  21. package/dist/containers/ReduxTooltip/ReduxTooltip.js +1 -1
  22. package/dist/containers/Tablets/Tablets.js +1 -1
  23. package/dist/containers/Tablets/Tablets.scss +0 -6
  24. package/dist/containers/TabletsFilters/TabletsFilters.scss +0 -7
  25. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +6 -2
  26. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.js +6 -2
  27. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +6 -2
  28. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +16 -2
  29. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +15 -13
  30. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +22 -6
  31. package/dist/containers/Tenant/Preview/Preview.js +3 -0
  32. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +20 -16
  33. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -0
  34. package/dist/types/api/schema.ts +36 -0
  35. package/dist/types/api/storage.ts +54 -0
  36. package/dist/utils/getNodesColumns.js +2 -0
  37. package/dist/utils/pdisk.ts +74 -0
  38. package/dist/utils/tooltip.js +27 -0
  39. package/dist/utils/utils.js +8 -1
  40. 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
- <span>{data.Path}</span>
290
- <EntityStatus status={data.Device} name={`${data.NodeId}-${data.PDiskId}`} />
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'} />
@@ -63,6 +63,7 @@ class Nodes extends React.Component {
63
63
  }
64
64
 
65
65
  componentWillUnmount() {
66
+ this.props.hideTooltip();
66
67
  clearInterval(this.reloadDescriptor);
67
68
  }
68
69
 
@@ -11,7 +11,7 @@ const propTypes = {
11
11
  className: PropTypes.string,
12
12
  toolTipVisible: PropTypes.bool,
13
13
  currentHoveredRef: PropTypes.object,
14
- data: PropTypes.object,
14
+ data: PropTypes.any,
15
15
  template: PropTypes.func,
16
16
  hideTooltip: PropTypes.func,
17
17
  };
@@ -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={(b(), className)}>
153
+ <div className={b(null, className)}>
154
154
  <div className={b('header')}>
155
155
  <Select
156
156
  className={b('filter-control')}
@@ -13,13 +13,7 @@
13
13
  }
14
14
 
15
15
  &__items {
16
- display: flex;
17
16
  flex: 1 1 auto;
18
- flex-wrap: wrap;
19
- }
20
-
21
- &__items-wrapper {
22
- overflow: auto;
23
17
  }
24
18
 
25
19
  &__filters {
@@ -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: 10px;
6
+ gap: 20px;
4
7
  &__section {
5
8
  display: flex;
6
- flex: 0 0 50%;
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 view="flat-info" onClick={showMoreHandler}>
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: 15px;
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
- <SchemaInfoViewer fullPath={currentItem.Path} data={schemaData} />
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>{Name}</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
- <div className={b('system-tablets')}>
143
- {SystemTablets &&
144
- SystemTablets.map((tablet, tabletIndex) => (
145
- <Tablet
146
- onMouseEnter={showTooltip}
147
- onMouseLeave={hideTooltip}
148
- key={tabletIndex}
149
- tablet={tablet}
150
- />
151
- ))}
152
- </div>
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: 20px;
34
+ margin-bottom: 10px;
35
+
36
+ line-height: 24px;
21
37
  }
22
38
 
23
39
  &__top-label {
24
- margin-bottom: 20px;
40
+ margin-bottom: detailedOverview.$section-title-margin;
25
41
 
26
42
  font-weight: 600;
27
- line-height: 24px;
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-left: 15px;
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: 500;
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 tableStatsInfo =
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 tabletMetricsInfo =
39
- TableStats &&
40
- Object.keys(TabletMetrics).map((key) => ({
41
- label: key,
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
- let generalInfo = Object.assign(tableStatsInfo, tabletMetricsInfo);
46
- generalInfo = Object.assign(generalInfo);
38
+ const tableStatsInfo = Object.keys(restTableStats).map((key) => ({
39
+ label: key,
40
+ value: TableStats[key].toString(),
41
+ }));
47
42
 
48
- const infoLength = Object.keys(generalInfo).length;
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
- {infoLength ? (
57
+ {generalInfo.length ? (
54
58
  <InfoViewer info={generalInfo}></InfoViewer>
55
59
  ) : (
56
60
  <div>Empty</div>
@@ -61,6 +61,7 @@ export function SchemaTree(props: SchemaTreeProps) {
61
61
  activePath={currentPath}
62
62
  onActivePathUpdate={handleActivePathUpdate}
63
63
  cache={false}
64
+ virtualize
64
65
  />
65
66
  );
66
67
  }
@@ -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
+ };
@@ -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
@@ -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
+ };