ydb-embedded-ui 4.16.1 → 4.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +4 -4
  3. package/dist/components/CircularProgressBar/CircularProgressBar.scss +42 -0
  4. package/dist/components/CircularProgressBar/CircularProgressBar.tsx +59 -0
  5. package/dist/components/DiagnosticCard/DiagnosticCard.scss +20 -3
  6. package/dist/components/DiagnosticCard/DiagnosticCard.tsx +5 -6
  7. package/dist/containers/Cluster/Cluster.tsx +4 -4
  8. package/dist/containers/Node/Node.tsx +4 -4
  9. package/dist/containers/Node/NodeStructure/NodeStructure.tsx +5 -5
  10. package/dist/containers/Nodes/Nodes.tsx +3 -3
  11. package/dist/containers/Storage/Storage.tsx +3 -3
  12. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +3 -3
  13. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +1 -5
  14. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +41 -18
  15. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -4
  16. package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Details/Details.tsx +3 -3
  17. package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Healthcheck.scss +27 -14
  18. package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Healthcheck.tsx +6 -6
  19. package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Preview/Preview.tsx +15 -16
  20. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/Healthcheck.scss +108 -0
  21. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckDetails.tsx +45 -0
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +83 -0
  23. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/IssuesViewer/IssueTree.scss +1 -3
  24. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/IssuesViewer/IssueTree.tsx +1 -1
  25. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/IssuesViewer/IssueTreeItem/IssueTreeItem.tsx +1 -1
  26. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/i18n/en.json +2 -1
  27. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/i18n/index.ts +1 -1
  28. package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/i18n/ru.json +2 -1
  29. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricCard/MetricCard.scss +52 -0
  30. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricCard/MetricCard.tsx +48 -0
  31. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.scss +12 -0
  32. package/dist/containers/Tenant/Diagnostics/TenantOverview/MetricsCards/MetricsCards.tsx +134 -0
  33. package/dist/containers/Tenant/Diagnostics/TenantOverview/OldTenantOverview.tsx +155 -0
  34. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +3 -5
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +79 -89
  36. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +3 -1
  37. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +3 -1
  38. package/dist/containers/Tenant/Diagnostics/TenantOverview/useHealthcheck.ts +53 -0
  39. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +5 -5
  40. package/dist/containers/Tenant/Tenant.tsx +4 -4
  41. package/dist/containers/Tenant/TenantPages.tsx +1 -0
  42. package/dist/containers/Tenant/utils/queryTemplates.ts +23 -23
  43. package/dist/containers/UserSettings/i18n/en.json +4 -1
  44. package/dist/containers/UserSettings/i18n/ru.json +4 -1
  45. package/dist/containers/UserSettings/settings.ts +7 -0
  46. package/dist/services/api.ts +6 -4
  47. package/dist/store/reducers/healthcheckInfo.ts +20 -12
  48. package/dist/store/reducers/settings/settings.ts +5 -0
  49. package/dist/store/reducers/tenant/constants.ts +7 -0
  50. package/dist/store/reducers/tenant/tenant.ts +15 -0
  51. package/dist/store/reducers/tenant/types.ts +5 -0
  52. package/dist/store/reducers/tenants/contants.ts +6 -0
  53. package/dist/store/reducers/tenants/types.ts +4 -0
  54. package/dist/store/reducers/tenants/utils.ts +114 -7
  55. package/dist/store/state-url-mapping.js +3 -0
  56. package/dist/styles/constants.scss +2 -0
  57. package/dist/types/api/tenant.ts +3 -0
  58. package/dist/utils/constants.ts +1 -0
  59. package/dist/utils/formatCPU/formatCPU.ts +20 -0
  60. package/dist/utils/formatCPU/i18n/en.json +3 -0
  61. package/dist/utils/formatCPU/i18n/index.ts +11 -0
  62. package/dist/utils/formatCPU/i18n/ru.json +3 -0
  63. package/package.json +1 -1
  64. /package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Details/index.ts +0 -0
  65. /package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/Preview/index.ts +0 -0
  66. /package/dist/containers/Tenant/Diagnostics/{Healthcheck → OldHealthcheck}/index.ts +0 -0
  67. /package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/IssuesViewer/IssueTreeItem/IssueTreeItem.scss +0 -0
  68. /package/dist/containers/Tenant/Diagnostics/{Healthcheck → TenantOverview/Healthcheck}/IssuesViewer/IssueTreeItem/index.ts +0 -0
@@ -350,10 +350,12 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
350
350
  enable_sampling: enableSampling,
351
351
  });
352
352
  }
353
- getHealthcheckInfo(database: string) {
354
- return this.get<HealthCheckAPIResponse>(this.getPath('/viewer/json/healthcheck'), {
355
- tenant: database,
356
- });
353
+ getHealthcheckInfo(database: string, {concurrentId}: AxiosOptions = {}) {
354
+ return this.get<HealthCheckAPIResponse>(
355
+ this.getPath('/viewer/json/healthcheck'),
356
+ {tenant: database},
357
+ {concurrentId},
358
+ );
357
359
  }
358
360
  killTablet(id?: string) {
359
361
  return this.get<string>(this.getPath(`/tablets?KillTabletID=${id}`), {});
@@ -44,6 +44,10 @@ const healthcheckInfo: Reducer<IHealthcheckInfoState, IHealthCheckInfoAction> =
44
44
  };
45
45
  }
46
46
  case FETCH_HEALTHCHECK.FAILURE: {
47
+ if (action.error?.isCancelled) {
48
+ return state;
49
+ }
50
+
47
51
  return {
48
52
  ...state,
49
53
  error: action.error,
@@ -72,21 +76,25 @@ const mapStatusToPriority: Partial<Record<StatusFlag, number>> = {
72
76
  GREEN: 4,
73
77
  };
74
78
 
75
- const getReasonsForIssue = ({issue, data}: {issue: IssueLog; data: IssueLog[]}) => {
76
- return data.filter((item) => issue.reason && issue.reason.indexOf(item.id) !== -1);
77
- };
79
+ const sortIssues = (data: IssueLog[]): IssueLog[] => {
80
+ return data.sort((a, b) => {
81
+ const aPriority = mapStatusToPriority[a.status] || 0;
82
+ const bPriority = mapStatusToPriority[b.status] || 0;
78
83
 
79
- const getRoots = (data: IssueLog[]): IssueLog[] => {
80
- let roots = data.filter((item) => {
81
- return !data.find((issue) => issue.reason && issue.reason.indexOf(item.id) !== -1);
84
+ return aPriority - bPriority;
82
85
  });
86
+ };
83
87
 
84
- roots = _flow([
85
- _uniqBy((item: IssueLog) => item.id),
86
- _sortBy(({status}: {status: StatusFlag}) => mapStatusToPriority[status]),
87
- ])(roots);
88
+ const getReasonsForIssue = ({issue, data}: {issue: IssueLog; data: IssueLog[]}) => {
89
+ return sortIssues(data.filter((item) => issue.reason && issue.reason.indexOf(item.id) !== -1));
90
+ };
88
91
 
89
- return roots;
92
+ const getRoots = (data: IssueLog[]): IssueLog[] => {
93
+ return sortIssues(
94
+ data.filter((item) => {
95
+ return !data.find((issue) => issue.reason && issue.reason.indexOf(item.id) !== -1);
96
+ }),
97
+ );
90
98
  };
91
99
 
92
100
  const getInvertedConsequencesTree = ({
@@ -147,7 +155,7 @@ export const selectIssuesStatistics: Selector<
147
155
 
148
156
  export function getHealthcheckInfo(database: string) {
149
157
  return createApiRequest({
150
- request: window.api.getHealthcheckInfo(database),
158
+ request: window.api.getHealthcheckInfo(database, {concurrentId: 'getHealthcheckInfo'}),
151
159
  actions: FETCH_HEALTHCHECK,
152
160
  });
153
161
  }
@@ -15,6 +15,7 @@ import {
15
15
  LAST_USED_QUERY_ACTION_KEY,
16
16
  USE_BACKEND_PARAMS_FOR_TABLES_KEY,
17
17
  LANGUAGE_KEY,
18
+ ENABLE_NEW_TENANT_DIAGNOSTICS_DESIGN,
18
19
  } from '../../../utils/constants';
19
20
  import '../../../services/api';
20
21
  import {parseJson} from '../../../utils/utils';
@@ -55,6 +56,10 @@ export const initialState = {
55
56
  ENABLE_ADDITIONAL_QUERY_MODES,
56
57
  'false',
57
58
  ),
59
+ [ENABLE_NEW_TENANT_DIAGNOSTICS_DESIGN]: readSavedSettingsValue(
60
+ ENABLE_NEW_TENANT_DIAGNOSTICS_DESIGN,
61
+ 'false',
62
+ ),
58
63
  [SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
59
64
  [TENANT_INITIAL_PAGE_KEY]: readSavedSettingsValue(
60
65
  TENANT_INITIAL_PAGE_KEY,
@@ -31,3 +31,10 @@ export const TENANT_SUMMARY_TABS_IDS = {
31
31
  acl: 'acl',
32
32
  schema: 'schema',
33
33
  } as const;
34
+
35
+ export const TENANT_METRICS_TABS_IDS = {
36
+ cpu: 'cpu',
37
+ storage: 'storage',
38
+ memory: 'memory',
39
+ healthcheck: 'healthcheck',
40
+ } as const;
@@ -4,6 +4,7 @@ import type {TTenant} from '../../../types/api/tenant';
4
4
  import type {
5
5
  TenantAction,
6
6
  TenantDiagnosticsTab,
7
+ TenantMetricsTab,
7
8
  TenantPage,
8
9
  TenantQueryTab,
9
10
  TenantState,
@@ -19,6 +20,7 @@ const SET_TOP_LEVEL_TAB = 'tenant/SET_TOP_LEVEL_TAB';
19
20
  const SET_QUERY_TAB = 'tenant/SET_QUERY_TAB';
20
21
  const SET_DIAGNOSTICS_TAB = 'tenant/SET_DIAGNOSTICS_TAB';
21
22
  const SET_SUMMARY_TAB = 'tenant/SET_SUMMARY_TAB';
23
+ const SET_METRICS_TAB = 'tenant/SET_METRICS_TAB';
22
24
  const CLEAR_TENANT = 'tenant/CLEAR_TENANT';
23
25
  const SET_DATA_WAS_NOT_LOADED = 'tenant/SET_DATA_WAS_NOT_LOADED';
24
26
 
@@ -88,6 +90,12 @@ const tenantReducer: Reducer<TenantState, TenantAction> = (state = initialState,
88
90
  summaryTab: action.data,
89
91
  };
90
92
  }
93
+ case SET_METRICS_TAB: {
94
+ return {
95
+ ...state,
96
+ metricsTab: action.data,
97
+ };
98
+ }
91
99
 
92
100
  case SET_DATA_WAS_NOT_LOADED: {
93
101
  return {
@@ -143,6 +151,13 @@ export function setSummaryTab(tab: TenantSummaryTab) {
143
151
  } as const;
144
152
  }
145
153
 
154
+ export function setMetricsTab(tab: TenantMetricsTab) {
155
+ return {
156
+ type: SET_METRICS_TAB,
157
+ data: tab,
158
+ } as const;
159
+ }
160
+
146
161
  export const setDataWasNotLoaded = () => {
147
162
  return {
148
163
  type: SET_DATA_WAS_NOT_LOADED,
@@ -8,6 +8,7 @@ import {
8
8
  TENANT_DIAGNOSTICS_TABS_IDS,
9
9
  TENANT_PAGES_IDS,
10
10
  TENANT_SUMMARY_TABS_IDS,
11
+ TENANT_METRICS_TABS_IDS,
11
12
  } from './constants';
12
13
  import {
13
14
  FETCH_TENANT,
@@ -15,6 +16,7 @@ import {
15
16
  setDiagnosticsTab,
16
17
  setQueryTab,
17
18
  setSummaryTab,
19
+ setMetricsTab,
18
20
  setTenantPage,
19
21
  setDataWasNotLoaded,
20
22
  } from './tenant';
@@ -24,6 +26,7 @@ export type TenantPage = ValueOf<typeof TENANT_PAGES_IDS>;
24
26
  export type TenantQueryTab = ValueOf<typeof TENANT_QUERY_TABS_ID>;
25
27
  export type TenantDiagnosticsTab = ValueOf<typeof TENANT_DIAGNOSTICS_TABS_IDS>;
26
28
  export type TenantSummaryTab = ValueOf<typeof TENANT_SUMMARY_TABS_IDS>;
29
+ export type TenantMetricsTab = ValueOf<typeof TENANT_METRICS_TABS_IDS>;
27
30
 
28
31
  export interface TenantState {
29
32
  loading: boolean;
@@ -32,6 +35,7 @@ export interface TenantState {
32
35
  queryTab?: TenantQueryTab;
33
36
  diagnosticsTab?: TenantDiagnosticsTab;
34
37
  summaryTab?: TenantSummaryTab;
38
+ metricsTab?: TenantMetricsTab;
35
39
  tenant?: TTenant;
36
40
  error?: IResponseError;
37
41
  }
@@ -43,4 +47,5 @@ export type TenantAction =
43
47
  | ReturnType<typeof setQueryTab>
44
48
  | ReturnType<typeof setDiagnosticsTab>
45
49
  | ReturnType<typeof setSummaryTab>
50
+ | ReturnType<typeof setMetricsTab>
46
51
  | ReturnType<typeof setDataWasNotLoaded>;
@@ -0,0 +1,6 @@
1
+ export const METRIC_STATUS = {
2
+ Unspecified: 'Unspecified',
3
+ Good: 'Good',
4
+ Warning: 'Warning',
5
+ Danger: 'Danger',
6
+ } as const;
@@ -1,8 +1,10 @@
1
1
  import {FETCH_TENANTS, setSearchValue} from './tenants';
2
2
 
3
+ import {ValueOf} from '../../../types/common';
3
4
  import type {TTenant} from '../../../types/api/tenant';
4
5
  import type {IResponseError} from '../../../types/api/error';
5
6
  import type {ApiRequestAction} from '../../utils';
7
+ import {METRIC_STATUS} from './contants';
6
8
 
7
9
  export interface PreparedTenant extends TTenant {
8
10
  backend: string | undefined;
@@ -30,3 +32,5 @@ export type TenantsAction =
30
32
  export interface TenantsStateSlice {
31
33
  tenants: TenantsState;
32
34
  }
35
+
36
+ export type MetricStatus = ValueOf<typeof METRIC_STATUS>;
@@ -1,5 +1,8 @@
1
1
  import type {TTenant} from '../../../types/api/tenant';
2
+ import {formatBytes} from '../../../utils/bytesParsers';
3
+ import {formatCPU} from '../../../utils/formatCPU/formatCPU';
2
4
  import {isNumeric} from '../../../utils/utils';
5
+ import {METRIC_STATUS} from './contants';
3
6
 
4
7
  const getControlPlaneValue = (tenant: TTenant) => {
5
8
  const parts = tenant.Name?.split('/');
@@ -18,21 +21,40 @@ const getTenantBackend = (tenant: TTenant) => {
18
21
  return node.Host ? `${node.Host}${address ? address : ''}` : undefined;
19
22
  };
20
23
 
21
- const calculateTenantMetrics = (tenant: TTenant) => {
22
- const {CoresUsed, MemoryUsed, StorageAllocatedSize, Metrics = {}} = tenant;
24
+ export const calculateTenantMetrics = (tenant?: TTenant) => {
25
+ const {
26
+ CoresUsed,
27
+ MemoryUsed,
28
+ StorageAllocatedSize,
29
+ CoresLimit,
30
+ MemoryLimit,
31
+ StorageLimit,
32
+ Metrics = {},
33
+ } = tenant || {};
23
34
 
24
35
  const cpuFromCores = isNumeric(CoresUsed) ? Number(CoresUsed) * 1_000_000 : undefined;
25
36
  const cpuFromMetrics = isNumeric(Metrics.CPU) ? Number(Metrics.CPU) : undefined;
26
37
 
27
- const cpu = cpuFromCores ?? cpuFromMetrics ?? 0;
38
+ const cpu = cpuFromCores ?? cpuFromMetrics ?? undefined;
28
39
 
29
40
  const rawMemory = MemoryUsed ?? Metrics.Memory;
30
- const rawStorage = StorageAllocatedSize ?? Metrics.Storage;
31
41
 
32
- const memory = isNumeric(rawMemory) ? Number(rawMemory) : 0;
33
- const storage = isNumeric(rawStorage) ? Number(rawStorage) : 0;
42
+ const memory = isNumeric(rawMemory) ? Number(rawMemory) : undefined;
34
43
 
35
- return {cpu, memory, storage};
44
+ // Blob storage - actual database size
45
+ const storage = isNumeric(StorageAllocatedSize) ? Number(StorageAllocatedSize) : undefined;
46
+ const cpuLimit = isNumeric(CoresLimit) ? Number(CoresLimit) : undefined;
47
+ const memoryLimit = isNumeric(MemoryLimit) ? Number(MemoryLimit) : undefined;
48
+ const storageLimit = isNumeric(StorageLimit) ? Number(StorageLimit) : undefined;
49
+
50
+ return {
51
+ cpu,
52
+ memory,
53
+ storage,
54
+ cpuLimit,
55
+ memoryLimit,
56
+ storageLimit,
57
+ };
36
58
  };
37
59
 
38
60
  const calculateTenantEntities = (tenant: TTenant) => {
@@ -66,3 +88,88 @@ export const prepareTenants = (tenants: TTenant[], useNodeAsBackend: boolean) =>
66
88
  };
67
89
  });
68
90
  };
91
+
92
+ export const calculateUsage = (valueUsed?: number, valueLimit?: number): number | undefined => {
93
+ if (valueUsed && valueLimit) {
94
+ return (valueUsed * 100) / valueLimit;
95
+ }
96
+
97
+ return undefined;
98
+ };
99
+
100
+ export const formatUsage = (usage?: number) => {
101
+ if (usage) {
102
+ return `${usage.toFixed()}%`;
103
+ }
104
+
105
+ return undefined;
106
+ };
107
+
108
+ export const cpuUsageToStatus = (usage?: number) => {
109
+ if (!usage) {
110
+ return METRIC_STATUS.Unspecified;
111
+ }
112
+
113
+ if (usage > 70) {
114
+ return METRIC_STATUS.Danger;
115
+ }
116
+ if (usage > 60) {
117
+ return METRIC_STATUS.Warning;
118
+ }
119
+
120
+ return METRIC_STATUS.Good;
121
+ };
122
+ export const storageUsageToStatus = (usage?: number) => {
123
+ if (!usage) {
124
+ return METRIC_STATUS.Unspecified;
125
+ }
126
+
127
+ if (usage > 85) {
128
+ return METRIC_STATUS.Danger;
129
+ }
130
+ if (usage > 75) {
131
+ return METRIC_STATUS.Warning;
132
+ }
133
+
134
+ return METRIC_STATUS.Good;
135
+ };
136
+
137
+ export const memoryUsageToStatus = (usage?: number) => {
138
+ if (!usage) {
139
+ return METRIC_STATUS.Unspecified;
140
+ }
141
+
142
+ if (usage > 70) {
143
+ return METRIC_STATUS.Danger;
144
+ }
145
+ if (usage > 60) {
146
+ return METRIC_STATUS.Warning;
147
+ }
148
+
149
+ return METRIC_STATUS.Good;
150
+ };
151
+
152
+ export const formatTenantMetrics = ({
153
+ cpu,
154
+ storage,
155
+ memory,
156
+ }: {
157
+ cpu?: number;
158
+ storage?: number;
159
+ memory?: number;
160
+ }) => ({
161
+ cpu: formatCPU(cpu),
162
+ storage: formatBytes({value: storage, significantDigits: 2}) || undefined,
163
+ memory: formatBytes({value: memory, significantDigits: 2}) || undefined,
164
+ });
165
+
166
+ export const normalizeProgress = (progress: number) => {
167
+ if (progress >= 100) {
168
+ return 100;
169
+ }
170
+ if (progress <= 0) {
171
+ return 0;
172
+ }
173
+
174
+ return progress;
175
+ };
@@ -54,6 +54,9 @@ const paramSetup = {
54
54
  summaryTab: {
55
55
  stateKey: 'tenant.summaryTab',
56
56
  },
57
+ metricsTab: {
58
+ stateKey: 'tenant.metricsTab',
59
+ },
57
60
  shardsMode: {
58
61
  stateKey: 'shardsWorkload.filters.mode',
59
62
  },
@@ -1,3 +1,5 @@
1
1
  :root {
2
2
  --tenant-object-info-max-value-width: 300px;
3
+
4
+ --diagnostics-section-title-margin: 20px;
3
5
  }
@@ -57,6 +57,9 @@ export interface TTenant {
57
57
 
58
58
  MonitoringEndpoint?: string; // additional
59
59
  ControlPlane?: ControlPlane; // additional
60
+
61
+ CoresLimit?: string; // TODO: check correctness in backend protos when fully supported
62
+ StorageLimit?: string; // TODO: check correctness in backend protos when fully supported
60
63
  }
61
64
 
62
65
  interface THiveDomainStatsStateCount {
@@ -88,6 +88,7 @@ export const LANGUAGE_KEY = 'language';
88
88
  export const INVERTED_DISKS_KEY = 'invertedDisks';
89
89
  export const USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY = 'useNodesEndpointInDiagnostics';
90
90
  export const ENABLE_ADDITIONAL_QUERY_MODES = 'enableAdditionalQueryModes';
91
+ export const ENABLE_NEW_TENANT_DIAGNOSTICS_DESIGN = 'enableNewTenantDiagnosticsDesign';
91
92
  export const SAVED_QUERIES_KEY = 'saved_queries';
92
93
  export const ASIDE_HEADER_COMPACT_KEY = 'asideHeaderCompact';
93
94
  export const QUERIES_HISTORY_KEY = 'queries_history';
@@ -0,0 +1,20 @@
1
+ import {configuredNumeral} from '../numeral';
2
+ import i18n from './i18n';
3
+
4
+ export const formatCPU = (value?: number) => {
5
+ if (value === undefined) {
6
+ return undefined;
7
+ }
8
+
9
+ const rawCores = value / 1000000;
10
+ let cores = rawCores.toPrecision(3);
11
+ if (rawCores >= 1000) {
12
+ cores = rawCores.toFixed();
13
+ }
14
+ if (rawCores < 0.001) {
15
+ cores = '0';
16
+ }
17
+ const localizedCores = configuredNumeral(Number(cores)).format('0.[000]');
18
+
19
+ return `${localizedCores} ${i18n('cores', {count: cores})}`;
20
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "cores": ["core", "cores", "cores", "cores"]
3
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-format-cpu';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,3 @@
1
+ {
2
+ "cores": ["ядро", "ядра", "ядер", "ядер"]
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.16.1",
3
+ "version": "4.17.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],