ydb-embedded-ui 1.10.1 → 1.10.2

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.1...v1.10.2) (2022-08-17)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * convert bytes on decimal scale ([db9b0a7](https://github.com/ydb-platform/ydb-embedded-ui/commit/db9b0a71fc5334f5a40992cc6abc0688782ad5d2))
9
+ * display HDD instead of ROT as pdisk type ([bd9e5ba](https://github.com/ydb-platform/ydb-embedded-ui/commit/bd9e5ba4e594cb3a1f6a964f619f9824e083ae7c))
10
+ * **InfoViewer:** accept default value formatter ([e03d8cc](https://github.com/ydb-platform/ydb-embedded-ui/commit/e03d8cc5de76e4ac00b05586ae6f6522a9708fb0))
11
+ * **InfoViewer:** allow longer labels ([89060a3](https://github.com/ydb-platform/ydb-embedded-ui/commit/89060a381858b5beaa3c3cf3402c13c917705676))
12
+ * **Overview:** display table r/o replicas ([6dbe0b4](https://github.com/ydb-platform/ydb-embedded-ui/commit/6dbe0b45fc5e3867f9d6141d270c15508a693e35))
13
+ * **Overview:** format & group table info in overview ([1a35cfc](https://github.com/ydb-platform/ydb-embedded-ui/commit/1a35cfcd2075454c4a1f1fc4961a4b3106b6d225))
14
+ * **QueryEditor:** save chosen run action ([b0fb436](https://github.com/ydb-platform/ydb-embedded-ui/commit/b0fb43651e0c6d1dc5d6a25f92716703402b556d))
15
+ * use current i18n lang for numeral formatting ([5d58fcf](https://github.com/ydb-platform/ydb-embedded-ui/commit/5d58fcffde21924f3cbe6c28946c7a9f755a8490))
16
+
3
17
  ## [1.10.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.0...v1.10.1) (2022-08-10)
4
18
 
5
19
 
@@ -10,13 +10,16 @@ const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
10
10
  ]);
11
11
 
12
12
  const formatItem = createInfoFormatter<TIndexDescription>({
13
- Type: (value) => value?.substring(10), // trims EIndexType prefix
14
- State: (value) => value?.substring(11), // trims EIndexState prefix
15
- KeyColumnNames: (value) => value?.join(', '),
16
- DataColumnNames: (value) => value?.join(', '),
17
- }, {
18
- KeyColumnNames: 'Columns',
19
- DataColumnNames: 'Includes',
13
+ values: {
14
+ Type: (value) => value?.substring(10), // trims EIndexType prefix
15
+ State: (value) => value?.substring(11), // trims EIndexState prefix
16
+ KeyColumnNames: (value) => value?.join(', '),
17
+ DataColumnNames: (value) => value?.join(', '),
18
+ },
19
+ labels: {
20
+ KeyColumnNames: 'Columns',
21
+ DataColumnNames: 'Includes',
22
+ },
20
23
  });
21
24
 
22
25
  interface IndexInfoViewerProps {
@@ -27,11 +27,10 @@
27
27
 
28
28
  &__label {
29
29
  display: flex;
30
- flex: 1 1 auto;
30
+ flex: 0 1 auto;
31
31
  align-items: baseline;
32
32
 
33
33
  min-width: 200px;
34
- max-width: 200px;
35
34
 
36
35
  white-space: nowrap;
37
36
 
@@ -2,7 +2,7 @@ type LabelMap<T> = {
2
2
  [label in keyof T]?: string;
3
3
  }
4
4
 
5
- type FieldMappers<T> = {
5
+ type ValueFormatters<T> = {
6
6
  [label in keyof T]?: (value: T[label]) => string | undefined;
7
7
  }
8
8
 
@@ -13,20 +13,28 @@ function formatLabel<Shape>(label: keyof Shape, map: LabelMap<Shape>) {
13
13
  function formatValue<Shape, Key extends keyof Shape>(
14
14
  label: Key,
15
15
  value: Shape[Key],
16
- mappers: FieldMappers<Shape>,
16
+ formatters: ValueFormatters<Shape>,
17
+ defaultFormatter?: (value: Shape[Key]) => string | undefined,
17
18
  ) {
18
- const mapper = mappers[label];
19
- const mappedValue = mapper ? mapper(value) : value;
19
+ const formatter = formatters[label] || defaultFormatter;
20
+ const formattedValue = formatter ? formatter(value) : value;
20
21
 
21
- return String(mappedValue ?? '');
22
+ return String(formattedValue ?? '');
22
23
  }
23
24
 
24
- export function createInfoFormatter<Shape extends Record<string, any>>(
25
- fieldMappers?: FieldMappers<Shape>,
26
- labelMap?: LabelMap<Shape>,
27
- ) {
25
+ interface CreateInfoFormatterOptions<Shape> {
26
+ values?: ValueFormatters<Shape>,
27
+ labels?: LabelMap<Shape>,
28
+ defaultValueFormatter?: (value: Shape[keyof Shape]) => string | undefined,
29
+ }
30
+
31
+ export function createInfoFormatter<Shape extends Record<string, any>>({
32
+ values: valueFormatters,
33
+ labels: labelMap,
34
+ defaultValueFormatter,
35
+ }: CreateInfoFormatterOptions<Shape>) {
28
36
  return <Key extends keyof Shape>(label: Key, value: Shape[Key]) => ({
29
37
  label: formatLabel(label, labelMap || {}),
30
- value: formatValue(label, value, fieldMappers || {}),
38
+ value: formatValue(label, value, valueFormatters || {}, defaultValueFormatter),
31
39
  });
32
40
  }
@@ -60,20 +60,21 @@ function DetailedOverview(props: DetailedOverviewProps) {
60
60
  const isTenant = tenantName === currentSchemaPath;
61
61
  return (
62
62
  <div className={b()}>
63
- <div className={b('section')}>
64
- {!isTenant && (
65
- <Overview type={type} tenantName={tenantName} />
66
- )}
67
- {isTenant && <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo}/>}
68
- </div>
69
- {isTenant && (
70
- <div className={b('section')}>
71
- <Healthcheck
72
- tenant={tenantName}
73
- preview={true}
74
- showMoreHandler={openModalHandler}
75
- />
76
- </div>
63
+ {isTenant ? (
64
+ <>
65
+ <div className={b('section')}>
66
+ <TenantOverview tenantName={tenantName} additionalTenantInfo={additionalTenantInfo} />
67
+ </div>
68
+ <div className={b('section')}>
69
+ <Healthcheck
70
+ tenant={tenantName}
71
+ preview={true}
72
+ showMoreHandler={openModalHandler}
73
+ />
74
+ </div>
75
+ </>
76
+ ) : (
77
+ <Overview type={type} tenantName={tenantName} />
77
78
  )}
78
79
  </div>
79
80
  );
@@ -33,6 +33,7 @@ import {
33
33
  DEFAULT_SIZE_RESULT_PANE_KEY,
34
34
  DEFAULT_TABLE_SETTINGS,
35
35
  SAVED_QUERIES_KEY,
36
+ QUERY_INITIAL_RUN_ACTION_KEY,
36
37
  } from '../../../utils/constants';
37
38
  import {prepareQueryResponse} from '../../../utils/index';
38
39
 
@@ -538,7 +539,13 @@ function QueryEditor(props) {
538
539
  };
539
540
 
540
541
  const renderControls = () => {
541
- const {executeQuery, explainQuery, savedQueries, selectRunAction} = props;
542
+ const {
543
+ executeQuery,
544
+ explainQuery,
545
+ savedQueries,
546
+ selectRunAction,
547
+ setSettingValue,
548
+ } = props;
542
549
  const {runAction} = executeQuery;
543
550
  const runIsDisabled = !executeQuery.input || executeQuery.loading;
544
551
  const runText = _.find(RUN_ACTIONS, {value: runAction}).content;
@@ -546,7 +553,10 @@ function QueryEditor(props) {
546
553
  const menuItems = RUN_ACTIONS.map((action) => {
547
554
  return {
548
555
  text: action.content,
549
- action: () => selectRunAction(action.value),
556
+ action: () => {
557
+ selectRunAction(action.value);
558
+ setSettingValue(QUERY_INITIAL_RUN_ACTION_KEY, action.value);
559
+ },
550
560
  };
551
561
  });
552
562
 
@@ -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} 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: (value) => value > 0 ? new Date(Number(value)).toUTCString() : 'N/A',
42
+ LastUpdateTime: (value) => value > 0 ? new Date(Number(value)).toUTCString() : 'N/A',
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
  },
@@ -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',
@@ -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,20 @@
1
1
  import numeral from 'numeral';
2
2
  import _ from 'lodash';
3
3
 
4
+ import {i18n} from './i18n';
4
5
  import {MEGABYTE, TERABYTE, DAY_IN_SECONDS, GIGABYTE} from './constants';
5
6
 
6
7
  import locales from 'numeral/locales'; // eslint-disable-line no-unused-vars
7
- numeral.locale('ru');
8
- numeral.localeData().delimiters.decimal = '.';
8
+
9
+ numeral.locale(i18n.lang);
9
10
 
10
11
  export const formatBytes = (bytes) => {
11
- return numeral(bytes).format('0 ib').replace('i', '');
12
+ // by agreement, display byte values in decimal scale
13
+ return numeral(bytes).format('0 b');
12
14
  };
13
15
 
16
+ export const formatBps = (bytes) => formatBytes(bytes) + '/s';
17
+
14
18
  export const formatBytesToGigabyte = (bytes) => {
15
19
  return `${Math.floor(bytes / GIGABYTE)} GB`;
16
20
  };
@@ -28,7 +28,7 @@ export const parseBitField = <T extends Record<string, number>>(
28
28
  };
29
29
 
30
30
  export enum IPDiskType {
31
- ROT = 'ROT',
31
+ HDD = 'HDD', // ROT (Rotation?) = HDD
32
32
  SSD = 'SSD',
33
33
  MVME = 'NVME',
34
34
  }
@@ -67,7 +67,7 @@ export const getPDiskType = (data: TPDiskStateInfo): IPDiskType | undefined => {
67
67
  return IPDiskType.MVME;
68
68
  }
69
69
  } else if (categoryBitField.typeExt === '0') {
70
- return IPDiskType.ROT;
70
+ return IPDiskType.HDD;
71
71
  }
72
72
 
73
73
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.10.1",
3
+ "version": "1.10.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],