ydb-embedded-ui 1.10.1 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +12 -9
  3. package/dist/components/InfoViewer/InfoViewer.scss +33 -9
  4. package/dist/components/InfoViewer/InfoViewer.tsx +43 -0
  5. package/dist/components/InfoViewer/index.ts +1 -0
  6. package/dist/components/InfoViewer/utils.ts +21 -11
  7. package/dist/components/Stack/Stack.scss +55 -0
  8. package/dist/components/Stack/Stack.tsx +35 -0
  9. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +2 -0
  10. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +5 -0
  11. package/dist/containers/Storage/Pdisk/Pdisk.scss +2 -19
  12. package/dist/containers/Storage/Pdisk/Pdisk.tsx +30 -33
  13. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +40 -0
  14. package/dist/containers/Storage/StorageGroups/StorageGroups.scss +25 -3
  15. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +31 -7
  16. package/dist/containers/Storage/Vdisk/Vdisk.js +63 -64
  17. package/dist/containers/Storage/Vdisk/Vdisk.scss +9 -28
  18. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +163 -0
  19. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +15 -14
  20. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -2
  21. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +164 -42
  22. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +18 -0
  23. package/dist/services/api.js +0 -1
  24. package/dist/setupTests.js +8 -0
  25. package/dist/store/reducers/executeQuery.js +3 -2
  26. package/dist/store/reducers/settings.js +20 -13
  27. package/dist/types/api/schema.ts +117 -4
  28. package/dist/types/api/storage.ts +121 -0
  29. package/dist/types/index.ts +1 -0
  30. package/dist/utils/constants.js +4 -0
  31. package/dist/utils/index.js +28 -4
  32. package/dist/utils/pdisk.ts +2 -2
  33. package/package.json +28 -5
  34. package/dist/components/InfoViewer/InfoViewer.js +0 -47
  35. package/dist/index.test.js +0 -5
@@ -3,63 +3,185 @@ import PropTypes from 'prop-types';
3
3
  import cn from 'bem-cn-lite';
4
4
  import './SchemaInfoViewer.scss';
5
5
 
6
- import {formatCPU, formatBytes} from '../../../../utils';
6
+ import {formatCPU, formatBytes, formatNumber, formatBps, formatDateTime} from '../../../../utils';
7
7
 
8
- import InfoViewer from '../../../../components/InfoViewer/InfoViewer';
8
+ import {InfoViewer, createInfoFormatter} from '../../../../components/InfoViewer';
9
9
 
10
10
  const b = cn('schema-info-viewer');
11
11
 
12
+ const formatTabletMetricsItem = createInfoFormatter({
13
+ values: {
14
+ CPU: formatCPU,
15
+ Memory: formatBytes,
16
+ Storage: formatBytes,
17
+ Network: formatBps,
18
+ ReadThroughput: formatBps,
19
+ WriteThroughput: formatBps,
20
+ },
21
+ defaultValueFormatter: formatNumber,
22
+ });
23
+
24
+ const formatFollowerGroupItem = createInfoFormatter({
25
+ values: {
26
+ FollowerCount: formatNumber,
27
+ },
28
+ });
29
+
30
+ const formatPartitionConfigItem = createInfoFormatter({
31
+ values: {
32
+ FollowerCount: formatNumber,
33
+ CrossDataCenterFollowerCount: formatNumber,
34
+ },
35
+ });
36
+
37
+ const formatTableStatsItem = createInfoFormatter({
38
+ values: {
39
+ DataSize: formatBytes,
40
+ IndexSize: formatBytes,
41
+ LastAccessTime: formatDateTime,
42
+ LastUpdateTime: formatDateTime,
43
+ },
44
+ defaultValueFormatter: formatNumber,
45
+ });
46
+
47
+ const formatTableStats = (fields) => Object.entries(fields)
48
+ .map(([label, value]) => formatTableStatsItem(label, value))
49
+ .filter(({value}) => Boolean(value));
50
+
12
51
  class SchemaInfoViewer extends React.Component {
13
52
  static propTypes = {
14
53
  data: PropTypes.object.isRequired,
15
54
  };
16
- formatTabletMetricsValue = (key, value) => {
17
- if (key === 'CPU') {
18
- return formatCPU(value);
19
- } else if (key === 'Memory' || key === 'Storage') {
20
- return formatBytes(value);
21
- } else {
22
- return value;
55
+
56
+ renderItem(itemData, title) {
57
+ if (!Array.isArray(itemData) || !itemData.length) {
58
+ return null;
23
59
  }
24
- };
60
+
61
+ return (
62
+ <div className={b('item')}>
63
+ <InfoViewer
64
+ title={title}
65
+ info={itemData}
66
+ />
67
+ </div>
68
+ );
69
+ }
70
+
71
+ renderContent(data) {
72
+ const {PathDescription = {}} = data;
73
+ const {TableStats = {}, TabletMetrics = {}, Table: {PartitionConfig = {}} = {}} = PathDescription;
74
+ const {
75
+ PartCount,
76
+ RowCount,
77
+ DataSize,
78
+ IndexSize,
79
+
80
+ LastAccessTime,
81
+ LastUpdateTime,
82
+
83
+ ImmediateTxCompleted,
84
+ PlannedTxCompleted,
85
+ TxRejectedByOverload,
86
+ TxRejectedBySpace,
87
+ TxCompleteLagMsec,
88
+ InFlightTxCount,
89
+
90
+ RowUpdates,
91
+ RowDeletes,
92
+ RowReads,
93
+ RangeReads,
94
+ RangeReadRows,
95
+
96
+ ...restTableStats
97
+ } = TableStats;
98
+ const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig;
99
+
100
+ const tableStatsInfo = [
101
+ formatTableStats({
102
+ PartCount,
103
+ RowCount,
104
+ DataSize,
105
+ IndexSize,
106
+ }),
107
+ formatTableStats({
108
+ LastAccessTime,
109
+ LastUpdateTime,
110
+ }),
111
+ formatTableStats({
112
+ ImmediateTxCompleted,
113
+ PlannedTxCompleted,
114
+ TxRejectedByOverload,
115
+ TxRejectedBySpace,
116
+ TxCompleteLagMsec,
117
+ InFlightTxCount,
118
+ }),
119
+ formatTableStats({
120
+ RowUpdates,
121
+ RowDeletes,
122
+ RowReads,
123
+ RangeReads,
124
+ RangeReadRows,
125
+ }),
126
+ formatTableStats(restTableStats),
127
+ ];
128
+
129
+ const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) =>
130
+ formatTabletMetricsItem(key, TabletMetrics[key])
131
+ );
132
+
133
+ const partitionConfigInfo = [];
134
+
135
+ if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
136
+ partitionConfigInfo.push(...Object.keys(FollowerGroups[0]).map((key) =>
137
+ formatFollowerGroupItem(key, FollowerGroups[0][key])
138
+ ));
139
+ } else if (FollowerCount !== undefined) {
140
+ partitionConfigInfo.push(
141
+ formatPartitionConfigItem('FollowerCount', FollowerCount)
142
+ );
143
+ } else if (CrossDataCenterFollowerCount !== undefined) {
144
+ partitionConfigInfo.push(
145
+ formatPartitionConfigItem('CrossDataCenterFollowerCount', CrossDataCenterFollowerCount)
146
+ );
147
+ }
148
+
149
+ if ([
150
+ tabletMetricsInfo,
151
+ partitionConfigInfo,
152
+ tableStatsInfo.flat(),
153
+ ].flat().length === 0) {
154
+ return (
155
+ <div className={b('item')}>Empty</div>
156
+ );
157
+ }
158
+
159
+ return (
160
+ <div className={b('row')}>
161
+ {tabletMetricsInfo.length > 0 || partitionConfigInfo.length > 0 ? (
162
+ <div className={b('col')}>
163
+ {this.renderItem(tabletMetricsInfo, 'Tablet Metrics')}
164
+ {this.renderItem(partitionConfigInfo, 'Partition Config')}
165
+ </div>
166
+ ) : null}
167
+ <div className={b('col')}>
168
+ {tableStatsInfo.map((info, index) => (
169
+ <React.Fragment key={index}>
170
+ {this.renderItem(info, index === 0 ? 'Table Stats' : undefined)}
171
+ </React.Fragment>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ );
176
+ }
177
+
25
178
  render() {
26
179
  const {data} = this.props;
27
180
 
28
181
  if (data) {
29
- const {PathDescription = {}} = data;
30
- const {TableStats = {}, TabletMetrics = {}} = PathDescription;
31
- const {PartCount, ...restTableStats} = TableStats;
32
-
33
- const priorityInfo = [{
34
- label: 'PartCount',
35
- value: PartCount,
36
- }].filter(({value}) => value !== undefined);
37
-
38
- const tableStatsInfo = Object.keys(restTableStats).map((key) => ({
39
- label: key,
40
- value: TableStats[key].toString(),
41
- }));
42
-
43
- const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) => ({
44
- label: key,
45
- value: this.formatTabletMetricsValue(key, TabletMetrics[key].toString()),
46
- }));
47
-
48
- const generalInfo = [
49
- ...priorityInfo,
50
- ...tabletMetricsInfo,
51
- ...tableStatsInfo,
52
- ];
53
-
54
182
  return (
55
183
  <div className={b()}>
56
- <div className={b('item')}>
57
- {generalInfo.length ? (
58
- <InfoViewer info={generalInfo}></InfoViewer>
59
- ) : (
60
- <div>Empty</div>
61
- )}
62
- </div>
184
+ {this.renderContent(data)}
63
185
  </div>
64
186
  );
65
187
  } else {
@@ -1,6 +1,24 @@
1
1
  .schema-info-viewer {
2
2
  overflow: auto;
3
3
 
4
+ &__row {
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ justify-content: flex-start;
8
+ align-items: flex-start;
9
+ }
10
+
11
+ &__col {
12
+ display: flex;
13
+ flex-direction: column;
14
+ justify-content: flex-start;
15
+ align-items: flex-start;
16
+
17
+ & + & {
18
+ margin-left: 50px;
19
+ }
20
+ }
21
+
4
22
  &__item {
5
23
  margin-bottom: 20px;
6
24
 
@@ -83,7 +83,6 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
83
83
  path,
84
84
  enums: true,
85
85
  backup: false,
86
- partition_config: false,
87
86
  partition_stats: false,
88
87
  partitioning_info: false,
89
88
  },
@@ -3,3 +3,11 @@
3
3
  // expect(element).toHaveTextContent(/react/i)
4
4
  // learn more: https://github.com/testing-library/jest-dom
5
5
  import '@testing-library/jest-dom';
6
+
7
+ import {configure as configureUiKit} from '@yandex-cloud/uikit';
8
+ import {configure as configureYdbUiComponents} from 'ydb-ui-components';
9
+ import {i18n, Lang} from '../src/utils/i18n';
10
+
11
+ i18n.setLang(Lang.En);
12
+ configureYdbUiComponents({lang: Lang.En});
13
+ configureUiKit({lang: Lang.En});
@@ -1,7 +1,8 @@
1
1
  import {createRequestActionTypes, createApiRequest} from '../utils';
2
2
  import '../../services/api';
3
3
  import {getValueFromLS, parseJson} from '../../utils/utils';
4
- import {QUERIES_HISTORY_KEY} from '../../utils/constants';
4
+ import {QUERIES_HISTORY_KEY, QUERY_INITIAL_RUN_ACTION_KEY} from '../../utils/constants';
5
+ import {readSavedSettingsValue} from './settings';
5
6
 
6
7
  const MAXIMUM_QUERIES_IN_HISTORY = 20;
7
8
 
@@ -39,7 +40,7 @@ const initialState = {
39
40
  ? MAXIMUM_QUERIES_IN_HISTORY - 1
40
41
  : queriesHistoryInitial.length - 1,
41
42
  },
42
- runAction: RUN_ACTIONS_VALUES.script,
43
+ runAction: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY, RUN_ACTIONS_VALUES.script),
43
44
  monacoHotKey: null,
44
45
  };
45
46
 
@@ -1,4 +1,11 @@
1
- import {ALL, defaultUserSettings, SAVED_QUERIES_KEY, THEME_KEY, TENANT_INITIAL_TAB_KEY} from '../../utils/constants';
1
+ import {
2
+ defaultUserSettings,
3
+ ALL,
4
+ SAVED_QUERIES_KEY,
5
+ THEME_KEY,
6
+ TENANT_INITIAL_TAB_KEY,
7
+ QUERY_INITIAL_RUN_ACTION_KEY,
8
+ } from '../../utils/constants';
2
9
  import '../../services/api';
3
10
  import {getValueFromLS} from '../../utils/utils';
4
11
 
@@ -7,24 +14,24 @@ const SET_SETTING_VALUE = 'settings/SET_VALUE';
7
14
 
8
15
  const userSettings = window.userSettings || {};
9
16
  const systemSettings = window.systemSettings || {};
10
- const theme = window.web_version
11
- ? userSettings.theme || 'light'
12
- : getValueFromLS(THEME_KEY, 'light');
13
- const savedQueries = window.web_version
14
- ? userSettings[SAVED_QUERIES_KEY]
15
- : getValueFromLS(SAVED_QUERIES_KEY, '[]');
16
- const savedTenantGeneralTab = window.web_version
17
- ? userSettings[TENANT_INITIAL_TAB_KEY]
18
- : getValueFromLS(TENANT_INITIAL_TAB_KEY);
17
+
18
+ export function readSavedSettingsValue(key, defaultValue) {
19
+ const savedValue = window.web_version
20
+ ? userSettings[key]
21
+ : getValueFromLS(key);
22
+
23
+ return savedValue ?? defaultValue;
24
+ }
19
25
 
20
26
  export const initialState = {
21
27
  problemFilter: ALL,
22
28
  userSettings: {
23
29
  ...defaultUserSettings,
24
30
  ...userSettings,
25
- theme,
26
- [SAVED_QUERIES_KEY]: savedQueries,
27
- [TENANT_INITIAL_TAB_KEY]: savedTenantGeneralTab,
31
+ theme: readSavedSettingsValue(THEME_KEY, 'light'),
32
+ [SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
33
+ [TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
34
+ [QUERY_INITIAL_RUN_ACTION_KEY]: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY),
28
35
  },
29
36
  systemSettings,
30
37
  };
@@ -16,7 +16,7 @@ export interface TEvDescribeSchemeResult {
16
16
  PathOwnerId?: string;
17
17
  }
18
18
 
19
- enum EStatus {
19
+ enum EStatus {
20
20
  StatusSuccess = 'StatusSuccess',
21
21
  StatusAccepted = 'StatusAccepted',
22
22
  StatusPathDoesNotExist = 'StatusPathDoesNotExist',
@@ -47,8 +47,8 @@ interface TPathDescription {
47
47
  Children?: TDirEntry[];
48
48
 
49
49
  // for table
50
- Table?: unknown;
51
- TableStats?: unknown;
50
+ Table?: TTableDescription;
51
+ TableStats?: TTableStats;
52
52
  TabletMetrics?: unknown;
53
53
  TablePartitions?: unknown[];
54
54
 
@@ -82,6 +82,119 @@ interface TDirEntry {
82
82
  Version?: TPathVersion;
83
83
  }
84
84
 
85
+ // incomplete
86
+ export interface TTableDescription {
87
+ PartitionConfig?: TPartitionConfig;
88
+ }
89
+
90
+ // incomplete
91
+ export interface TPartitionConfig {
92
+ /** uint64 */
93
+ FollowerCount?: string;
94
+ /**
95
+ * uint32
96
+ * @deprecated use FollowerGroups
97
+ */
98
+ CrossDataCenterFollowerCount?: string;
99
+ /** 0 or 1 items */
100
+ FollowerGroups?: TFollowerGroup[];
101
+ }
102
+
103
+ export interface TFollowerGroup {
104
+ /** uint32 */
105
+ FollowerCount?: string;
106
+ AllowLeaderPromotion?: boolean;
107
+ AllowClientRead?: boolean;
108
+ /** uint32[] */
109
+ AllowedNodeIDs?: string[];
110
+ /**
111
+ * uint32[]
112
+ * @deprecated use AllowedDataCenters
113
+ */
114
+ AllowedDataCenterNumIDs?: string[];
115
+ RequireAllDataCenters?: boolean;
116
+ LocalNodeOnly?: boolean;
117
+ RequireDifferentNodes?: boolean;
118
+ FollowerCountPerDataCenter?: boolean; // multiplies FollowerCount by number of DataCenters
119
+ AllowedDataCenters?: string[];
120
+ }
121
+
122
+ interface TTableStats {
123
+ /** uint64 */
124
+ DataSize?: string;
125
+ /** uint64 */
126
+ RowCount?: string;
127
+ /** uint64 */
128
+ IndexSize?: string;
129
+ /** uint64 */
130
+ InMemSize?: string;
131
+
132
+ /**
133
+ * uint64
134
+ * unix time in millisec
135
+ */
136
+ LastAccessTime?: string;
137
+ /**
138
+ * uint64
139
+ * unix time in millisec
140
+ */
141
+ LastUpdateTime?: string;
142
+
143
+ RowCountHistogram?: THistogram;
144
+ DataSizeHistogram?: THistogram;
145
+
146
+ /** uint64 */
147
+ ImmediateTxCompleted?: string;
148
+ /** uint64 */
149
+ PlannedTxCompleted?: string;
150
+ /** uint64 */
151
+ TxRejectedByOverload?: string;
152
+ /** uint64 */
153
+ TxRejectedBySpace?: string;
154
+ /** uint64 */
155
+ TxCompleteLagMsec?: string;
156
+ /** uint64 */
157
+ InFlightTxCount?: string;
158
+
159
+ /** uint64 */
160
+ RowUpdates?: string;
161
+ /** uint64 */
162
+ RowDeletes?: string;
163
+ /** uint64 */
164
+ RowReads?: string;
165
+ /** uint64 */
166
+ RangeReads?: string;
167
+ /** uint64 */
168
+ RangeReadRows?: string;
169
+
170
+ /** uint64 */
171
+ PartCount?: string;
172
+
173
+ KeyAccessSample?: THistogram;
174
+
175
+ /** uint64 */
176
+ SearchHeight?: string;
177
+
178
+ /**
179
+ * uint64
180
+ * seconds since epoch
181
+ */
182
+ LastFullCompactionTs?: string;
183
+
184
+ // i.e. this shard lent to other shards
185
+ HasLoanedParts?: boolean;
186
+ }
187
+
188
+ interface THistogram {
189
+ Buckets?: THistogramBucket[];
190
+ }
191
+
192
+ interface THistogramBucket {
193
+ Key?: string;
194
+ /** uint64 */
195
+ Value?: string;
196
+ }
197
+
85
198
  export interface TIndexDescription {
86
199
  Name?: string;
87
200
  /** uint64 */
@@ -111,7 +224,7 @@ export enum EPathType {
111
224
 
112
225
  EPathTypeSubDomain = 'EPathTypeSubDomain',
113
226
 
114
- EPathTypeTableIndex = 'EPathTypeTableIndex',
227
+ EPathTypeTableIndex = 'EPathTypeTableIndex',
115
228
  EPathTypeExtSubDomain = 'EPathTypeExtSubDomain',
116
229
 
117
230
  EPathTypeColumnStore = 'EPathTypeColumnStore',
@@ -52,3 +52,124 @@ export interface TPDiskStateInfo {
52
52
  Overall?: EFlag;
53
53
  SerialNumber?: string;
54
54
  }
55
+
56
+ export enum EVDiskState {
57
+ Initial = 'Initial',
58
+ LocalRecoveryError = 'LocalRecoveryError',
59
+ SyncGuidRecovery = 'SyncGuidRecovery',
60
+ SyncGuidRecoveryError = 'SyncGuidRecoveryError',
61
+ OK = 'OK',
62
+ PDiskError = 'PDiskError',
63
+ }
64
+
65
+ interface TRank {
66
+ /**
67
+ * uint32
68
+ * Rank in percents; 0-100% is good; >100% is bad.
69
+ * Formula for rank calculation is the following:
70
+ * Rank = actual_value / max_allowed_value * 100
71
+ */
72
+ RankPercent?: string;
73
+
74
+ /**
75
+ * Flag is the Rank transformed to something simple
76
+ * to understand: Green, Yellow or Red
77
+ */
78
+ Flag?: EFlag;
79
+ }
80
+
81
+ interface TVDiskSatisfactionRank {
82
+ FreshRank?: TRank;
83
+ LevelRank?: TRank;
84
+ }
85
+
86
+ interface TVDiskID {
87
+ /** uint32 */
88
+ GroupID?: string;
89
+ /** uint32 */
90
+ GroupGeneration?: string;
91
+ /** uint32 */
92
+ Ring?: string;
93
+ /** uint32 */
94
+ Domain?: string;
95
+ /** uint32 */
96
+ VDisk?: string;
97
+ }
98
+
99
+ export interface TVDiskStateInfo {
100
+ VDiskId?: TVDiskID;
101
+ /** uint64 */
102
+ CreateTime?: string;
103
+ /** uint64 */
104
+ ChangeTime?: string;
105
+ /** uint32 */
106
+ PDiskId?: string;
107
+ /** uint32 */
108
+ VDiskSlotId?: string;
109
+ /** uint64 */
110
+ Guid?: string;
111
+ /** uint64 */
112
+ Kind?: string;
113
+ /** uint32 */
114
+ NodeId?: string;
115
+ /** uint32 */
116
+ Count?: string;
117
+
118
+ Overall?: EFlag;
119
+
120
+ /** Current state of VDisk */
121
+ VDiskState?: EVDiskState;
122
+ /** Disk space flags */
123
+ DiskSpace?: EFlag;
124
+ /** Compaction satisfaction rank */
125
+ SatisfactionRank?: TVDiskSatisfactionRank;
126
+ /** Is VDisk replicated? (i.e. contains all blobs it must have) */
127
+ Replicated?: boolean;
128
+ /** Does this VDisk has any yet unreplicated phantom-like blobs? */
129
+ UnreplicatedPhantoms?: boolean;
130
+ /** The same for the non-phantom-like blobs. */
131
+ UnreplicatedNonPhantoms?: boolean;
132
+ /**
133
+ * uint64
134
+ * How many unsynced VDisks from current BlobStorage group we see
135
+ */
136
+ UnsyncedVDisks?: string;
137
+ /**
138
+ * uint64
139
+ * How much this VDisk have allocated on corresponding PDisk
140
+ */
141
+ AllocatedSize?: string;
142
+ /**
143
+ * uint64
144
+ * How much space is available for VDisk corresponding to PDisk's hard space limits
145
+ */
146
+ AvailableSize?: string;
147
+ /** Does this disk has some unreadable but not yet restored blobs? */
148
+ HasUnreadableBlobs?: boolean;
149
+ /** fixed64 */
150
+ IncarnationGuid?: string;
151
+ DonorMode?: boolean;
152
+ /**
153
+ * fixed64
154
+ * VDisk actor instance guid
155
+ */
156
+ InstanceGuid?: string;
157
+ Donors?: TVDiskStateInfo[];
158
+
159
+ /** VDisk (Skeleton) Front Queue Status */
160
+ FrontQueues?: EFlag;
161
+
162
+ /** VDisk storage pool label */
163
+ StoragePoolName?: string;
164
+
165
+ /**
166
+ * uint64
167
+ * Read bytes per second from PDisk for TEvVGet blobs only
168
+ */
169
+ ReadThroughput?: string;
170
+ /**
171
+ * uint64
172
+ * Write bytes per second to PDisk for TEvVPut blobs and replication bytes only
173
+ */
174
+ WriteThroughput?: string;
175
+ }
@@ -0,0 +1 @@
1
+ export type RequiredField<Src, Fields extends keyof Src> = Src & Required<Pick<Src, Fields>>;
@@ -6,6 +6,9 @@ export const GROUP_AUTO_RELOAD_INTERVAL = 10 * SECOND;
6
6
  export const PDISK_AUTO_RELOAD_INTERVAL = 10 * SECOND;
7
7
  export const VDISK_AUTO_RELOAD_INTERVAL = 10 * SECOND;
8
8
  export const AUTO_RELOAD_INTERVAL = 10 * SECOND;
9
+ // by agreement, display all byte values in decimal scale
10
+ // values in data are always in bytes, never in higher units,
11
+ // therefore there is no issue arbitrary converting them in UI
9
12
  export const MEGABYTE = 1_000_000;
10
13
  export const GIGABYTE = 1_000_000_000;
11
14
  export const TERABYTE = 1_000_000_000_000;
@@ -139,3 +142,4 @@ export const DEFAULT_TABLE_SETTINGS = {
139
142
  };
140
143
 
141
144
  export const TENANT_INITIAL_TAB_KEY = 'saved_tenant_initial_tab';
145
+ export const QUERY_INITIAL_RUN_ACTION_KEY = 'query_initial_run_action';
@@ -1,16 +1,24 @@
1
1
  import numeral from 'numeral';
2
+ import locales from 'numeral/locales'; // eslint-disable-line no-unused-vars
2
3
  import _ from 'lodash';
3
4
 
5
+ import {i18n} from './i18n';
4
6
  import {MEGABYTE, TERABYTE, DAY_IN_SECONDS, GIGABYTE} from './constants';
7
+ import {isNumeric} from './utils';
5
8
 
6
- import locales from 'numeral/locales'; // eslint-disable-line no-unused-vars
7
- numeral.locale('ru');
8
- numeral.localeData().delimiters.decimal = '.';
9
+ numeral.locale(i18n.lang);
9
10
 
10
11
  export const formatBytes = (bytes) => {
11
- return numeral(bytes).format('0 ib').replace('i', '');
12
+ if (!isNumeric(bytes)) {
13
+ return '';
14
+ }
15
+
16
+ // by agreement, display byte values in decimal scale
17
+ return numeral(bytes).format('0 b');
12
18
  };
13
19
 
20
+ export const formatBps = (bytes) => formatBytes(bytes) + '/s';
21
+
14
22
  export const formatBytesToGigabyte = (bytes) => {
15
23
  return `${Math.floor(bytes / GIGABYTE)} GB`;
16
24
  };
@@ -49,13 +57,29 @@ export const formatThroughput = (value, total) => {
49
57
  };
50
58
 
51
59
  export const formatNumber = (number) => {
60
+ if (!isNumeric(number)) {
61
+ return '';
62
+ }
63
+
52
64
  return numeral(number).format();
53
65
  };
54
66
 
55
67
  export const formatCPU = (value) => {
68
+ if (!isNumeric(value)) {
69
+ return '';
70
+ }
71
+
56
72
  return numeral(value / 1000000).format('0.00');
57
73
  };
58
74
 
75
+ export const formatDateTime = (value) => {
76
+ if (!isNumeric(value)) {
77
+ return '';
78
+ }
79
+
80
+ return value > 0 ? new Date(Number(value)).toUTCString() : 'N/A';
81
+ };
82
+
59
83
  export const calcUptime = (milliseconds) => {
60
84
  const currentDate = new Date();
61
85
  return formatUptime((currentDate - Number(milliseconds)) / 1000);