ydb-embedded-ui 1.10.1 → 1.11.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 (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);