ydb-embedded-ui 4.9.0 → 4.10.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.
- package/CHANGELOG.md +12 -0
- package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +7 -4
- package/dist/components/EntityStatus/EntityStatus.js +3 -1
- package/dist/components/FormattedBytes/FormattedBytes.tsx +10 -0
- package/dist/components/FormattedBytes/utils.tsx +13 -0
- package/dist/components/FullNodeViewer/FullNodeViewer.tsx +73 -0
- package/dist/components/InfoViewer/formatters/table.ts +6 -5
- package/dist/components/ProblemFilter/ProblemFilter.tsx +2 -2
- package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +4 -4
- package/dist/components/TruncatedQuery/{TruncatedQuery.js → TruncatedQuery.tsx} +10 -8
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +6 -6
- package/dist/containers/Cluster/Cluster.tsx +10 -6
- package/dist/containers/Node/Node.tsx +3 -3
- package/dist/containers/Nodes/Nodes.tsx +2 -2
- package/dist/containers/Storage/PDisk/PDisk.tsx +2 -7
- package/dist/containers/Storage/Storage.tsx +240 -0
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +45 -40
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +12 -16
- package/dist/containers/Storage/UsageFilter/UsageFilter.scss +1 -0
- package/dist/containers/Storage/UsageFilter/UsageFilter.tsx +17 -17
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -3
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -4
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +0 -15
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +10 -3
- package/dist/containers/Tenant/{Preview → Query/Preview}/Preview.scss +1 -1
- package/dist/containers/Tenant/Query/Preview/Preview.tsx +121 -0
- package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +1 -1
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +6 -8
- package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +1 -1
- package/dist/containers/Tenant/Query/i18n/en.json +8 -1
- package/dist/containers/Tenant/Query/i18n/ru.json +8 -1
- package/dist/containers/Tenants/Tenants.tsx +269 -0
- package/dist/services/api.ts +8 -3
- package/dist/store/reducers/nodes/nodes.ts +4 -4
- package/dist/store/reducers/partitions/types.ts +3 -3
- package/dist/store/reducers/settings/settings.ts +4 -2
- package/dist/store/reducers/settings/types.ts +3 -1
- package/dist/store/reducers/storage/selectors.ts +279 -0
- package/dist/store/reducers/storage/storage.ts +191 -0
- package/dist/store/reducers/storage/types.ts +80 -0
- package/dist/store/reducers/tenants/selectors.ts +46 -0
- package/dist/store/reducers/tenants/tenants.ts +21 -14
- package/dist/store/reducers/tenants/types.ts +20 -5
- package/dist/store/reducers/tenants/utils.ts +68 -0
- package/dist/types/additionalProps.ts +8 -0
- package/dist/types/api/storage.ts +1 -1
- package/dist/types/store/topic.ts +3 -3
- package/dist/utils/bytesParsers/__test__/formatBytes.test.ts +38 -0
- package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +2 -2
- package/dist/utils/bytesParsers/formatBytes.ts +132 -0
- package/dist/utils/bytesParsers/i18n/en.json +1 -0
- package/dist/utils/bytesParsers/i18n/ru.json +1 -0
- package/dist/utils/bytesParsers/index.ts +1 -1
- package/dist/utils/index.js +5 -10
- package/dist/utils/numeral.ts +8 -0
- package/package.json +1 -1
- package/dist/components/FullNodeViewer/FullNodeViewer.js +0 -89
- package/dist/containers/Node/NodeOverview/NodeOverview.scss +0 -0
- package/dist/containers/Node/NodeOverview/NodeOverview.tsx +0 -21
- package/dist/containers/Storage/Storage.js +0 -350
- package/dist/containers/Tenant/Preview/Preview.js +0 -168
- package/dist/containers/Tenants/Tenants.js +0 -363
- package/dist/store/reducers/storage/storage.js +0 -404
- package/dist/utils/bytesParsers/formatBytesCustom.ts +0 -57
@@ -1,404 +0,0 @@
|
|
1
|
-
import _ from 'lodash';
|
2
|
-
import {createSelector} from 'reselect';
|
3
|
-
|
4
|
-
import {calcUptime} from '../../../utils';
|
5
|
-
import {getUsage} from '../../../utils/storage';
|
6
|
-
import {NodesUptimeFilterValues} from '../../../utils/nodes';
|
7
|
-
import {getPDiskType} from '../../../utils/pdisk';
|
8
|
-
import '../../../services/api';
|
9
|
-
|
10
|
-
import {createRequestActionTypes, createApiRequest} from '../../utils';
|
11
|
-
import {filterNodesByUptime} from '../nodes/nodes';
|
12
|
-
|
13
|
-
import {VISIBLE_ENTITIES, STORAGE_TYPES} from './constants';
|
14
|
-
|
15
|
-
const FETCH_STORAGE = createRequestActionTypes('storage', 'FETCH_STORAGE');
|
16
|
-
const SET_INITIAL = 'storage/SET_INITIAL';
|
17
|
-
const SET_FILTER = 'storage/SET_FILTER';
|
18
|
-
const SET_USAGE_FILTER = 'storage/SET_USAGE_FILTER';
|
19
|
-
const SET_VISIBLE_GROUPS = 'storage/SET_VISIBLE_GROUPS';
|
20
|
-
const SET_STORAGE_TYPE = 'storage/SET_STORAGE_TYPE';
|
21
|
-
const SET_NODES_UPTIME_FILTER = 'storage/SET_NODES_UPTIME_FILTER';
|
22
|
-
const SET_DATA_WAS_NOT_LOADED = 'storage/SET_DATA_WAS_NOT_LOADED';
|
23
|
-
|
24
|
-
const initialState = {
|
25
|
-
loading: true,
|
26
|
-
wasLoaded: false,
|
27
|
-
filter: '',
|
28
|
-
usageFilter: [],
|
29
|
-
visible: VISIBLE_ENTITIES.missing,
|
30
|
-
nodesUptimeFilter: NodesUptimeFilterValues.All,
|
31
|
-
type: STORAGE_TYPES.groups,
|
32
|
-
};
|
33
|
-
|
34
|
-
const storage = (state = initialState, action) => {
|
35
|
-
switch (action.type) {
|
36
|
-
case FETCH_STORAGE.REQUEST: {
|
37
|
-
return {
|
38
|
-
...state,
|
39
|
-
loading: true,
|
40
|
-
};
|
41
|
-
}
|
42
|
-
case FETCH_STORAGE.SUCCESS: {
|
43
|
-
return {
|
44
|
-
...state,
|
45
|
-
data: action.data,
|
46
|
-
loading: false,
|
47
|
-
wasLoaded: true,
|
48
|
-
error: undefined,
|
49
|
-
};
|
50
|
-
}
|
51
|
-
case FETCH_STORAGE.FAILURE: {
|
52
|
-
if (action.error?.isCancelled) {
|
53
|
-
return state;
|
54
|
-
}
|
55
|
-
|
56
|
-
return {
|
57
|
-
...state,
|
58
|
-
error: action.error,
|
59
|
-
loading: false,
|
60
|
-
wasLoaded: true,
|
61
|
-
};
|
62
|
-
}
|
63
|
-
case SET_INITIAL: {
|
64
|
-
return {
|
65
|
-
...initialState,
|
66
|
-
};
|
67
|
-
}
|
68
|
-
case SET_FILTER: {
|
69
|
-
return {
|
70
|
-
...state,
|
71
|
-
filter: action.data,
|
72
|
-
};
|
73
|
-
}
|
74
|
-
case SET_USAGE_FILTER: {
|
75
|
-
return {
|
76
|
-
...state,
|
77
|
-
usageFilter: action.data,
|
78
|
-
};
|
79
|
-
}
|
80
|
-
case SET_VISIBLE_GROUPS: {
|
81
|
-
return {
|
82
|
-
...state,
|
83
|
-
visible: action.data,
|
84
|
-
wasLoaded: false,
|
85
|
-
error: undefined,
|
86
|
-
};
|
87
|
-
}
|
88
|
-
|
89
|
-
case SET_NODES_UPTIME_FILTER: {
|
90
|
-
return {
|
91
|
-
...state,
|
92
|
-
nodesUptimeFilter: action.data,
|
93
|
-
};
|
94
|
-
}
|
95
|
-
case SET_STORAGE_TYPE: {
|
96
|
-
return {
|
97
|
-
...state,
|
98
|
-
type: action.data,
|
99
|
-
filter: '',
|
100
|
-
usageFilter: [],
|
101
|
-
wasLoaded: false,
|
102
|
-
error: undefined,
|
103
|
-
};
|
104
|
-
}
|
105
|
-
case SET_DATA_WAS_NOT_LOADED: {
|
106
|
-
return {
|
107
|
-
...state,
|
108
|
-
wasLoaded: false,
|
109
|
-
};
|
110
|
-
}
|
111
|
-
default:
|
112
|
-
return state;
|
113
|
-
}
|
114
|
-
};
|
115
|
-
|
116
|
-
export function setInitialState() {
|
117
|
-
return {
|
118
|
-
type: SET_INITIAL,
|
119
|
-
};
|
120
|
-
}
|
121
|
-
|
122
|
-
export function getStorageInfo({tenant, visibleEntities, nodeId, type}, {concurrentId}) {
|
123
|
-
if (type === STORAGE_TYPES.nodes) {
|
124
|
-
return createApiRequest({
|
125
|
-
request: window.api.getNodes({tenant, visibleEntities, type: 'static'}, {concurrentId}),
|
126
|
-
actions: FETCH_STORAGE,
|
127
|
-
});
|
128
|
-
}
|
129
|
-
|
130
|
-
return createApiRequest({
|
131
|
-
request: window.api.getStorageInfo({tenant, visibleEntities, nodeId}, {concurrentId}),
|
132
|
-
actions: FETCH_STORAGE,
|
133
|
-
});
|
134
|
-
}
|
135
|
-
|
136
|
-
export function setStorageType(value) {
|
137
|
-
return {
|
138
|
-
type: SET_STORAGE_TYPE,
|
139
|
-
data: value,
|
140
|
-
};
|
141
|
-
}
|
142
|
-
|
143
|
-
export function setStorageFilter(value) {
|
144
|
-
return {
|
145
|
-
type: SET_FILTER,
|
146
|
-
data: value,
|
147
|
-
};
|
148
|
-
}
|
149
|
-
|
150
|
-
export function setUsageFilter(value) {
|
151
|
-
return {
|
152
|
-
type: SET_USAGE_FILTER,
|
153
|
-
data: value,
|
154
|
-
};
|
155
|
-
}
|
156
|
-
|
157
|
-
export function setVisibleEntities(value) {
|
158
|
-
return {
|
159
|
-
type: SET_VISIBLE_GROUPS,
|
160
|
-
data: value,
|
161
|
-
};
|
162
|
-
}
|
163
|
-
|
164
|
-
export function setNodesUptimeFilter(value) {
|
165
|
-
return {
|
166
|
-
type: SET_NODES_UPTIME_FILTER,
|
167
|
-
data: value,
|
168
|
-
};
|
169
|
-
}
|
170
|
-
|
171
|
-
export const setDataWasNotLoaded = () => {
|
172
|
-
return {
|
173
|
-
type: SET_DATA_WAS_NOT_LOADED,
|
174
|
-
};
|
175
|
-
};
|
176
|
-
|
177
|
-
export const getStoragePools = (state) => state.storage.data?.StoragePools;
|
178
|
-
export const getStoragePoolsGroupsCount = (state) => ({
|
179
|
-
total: state.storage.data?.TotalGroups || 0,
|
180
|
-
found: state.storage.data?.FoundGroups || 0,
|
181
|
-
});
|
182
|
-
export const getStorageNodes = (state) => state.storage.data?.Nodes;
|
183
|
-
export const getStorageNodesCount = (state) => ({
|
184
|
-
total: state.storage.data?.TotalNodes || 0,
|
185
|
-
found: state.storage.data?.FoundNodes || 0,
|
186
|
-
});
|
187
|
-
export const getStorageFilter = (state) => state.storage.filter;
|
188
|
-
export const getUsageFilter = (state) => state.storage.usageFilter;
|
189
|
-
export const getVisibleEntities = (state) => state.storage.visible;
|
190
|
-
export const getNodesUptimeFilter = (state) => state.storage.nodesUptimeFilter;
|
191
|
-
export const getStorageType = (state) => state.storage.type;
|
192
|
-
|
193
|
-
const FLAGS_POINTS = {
|
194
|
-
Green: 1,
|
195
|
-
Yellow: 100,
|
196
|
-
Orange: 10_000,
|
197
|
-
Red: 1_000_000,
|
198
|
-
};
|
199
|
-
export const getFlatListStorageGroups = createSelector([getStoragePools], (storagePools) => {
|
200
|
-
return _.reduce(
|
201
|
-
storagePools,
|
202
|
-
(acc, pool) => {
|
203
|
-
const groups = _.reduce(
|
204
|
-
pool.Groups,
|
205
|
-
(acc, group) => {
|
206
|
-
const missing = _.filter(group.VDisks, (v) => {
|
207
|
-
return !v.Replicated || v.PDisk.State !== 'Normal' || v.VDiskState !== 'OK';
|
208
|
-
}).length;
|
209
|
-
|
210
|
-
const UsedSpaceFlag = _.reduce(
|
211
|
-
group.VDisks,
|
212
|
-
(acc, v) => {
|
213
|
-
if (v.DiskSpace) {
|
214
|
-
acc += FLAGS_POINTS[v.DiskSpace];
|
215
|
-
}
|
216
|
-
return acc;
|
217
|
-
},
|
218
|
-
0,
|
219
|
-
);
|
220
|
-
|
221
|
-
const usedSpaceBytes = _.reduce(
|
222
|
-
group.VDisks,
|
223
|
-
(acc, vDisk) => {
|
224
|
-
return acc + (Number(vDisk.AllocatedSize) || 0);
|
225
|
-
},
|
226
|
-
0,
|
227
|
-
);
|
228
|
-
const limitSizeBytes = _.reduce(
|
229
|
-
group.VDisks,
|
230
|
-
(acc, vDisk) => {
|
231
|
-
const {AvailableSize, AllocatedSize, PDisk} = vDisk;
|
232
|
-
const available = (AvailableSize ?? PDisk?.AvailableSize) || 0;
|
233
|
-
const allocated = AllocatedSize || 0;
|
234
|
-
|
235
|
-
return acc + Number(available) + Number(allocated);
|
236
|
-
},
|
237
|
-
0,
|
238
|
-
);
|
239
|
-
const readSpeedBytesPerSec = _.reduce(
|
240
|
-
group.VDisks,
|
241
|
-
(acc, vDisk) => {
|
242
|
-
return acc + (Number(vDisk.ReadThroughput) || 0);
|
243
|
-
},
|
244
|
-
0,
|
245
|
-
);
|
246
|
-
const writeSpeedBytesPerSec = _.reduce(
|
247
|
-
group.VDisks,
|
248
|
-
(acc, vDisk) => {
|
249
|
-
return acc + (Number(vDisk.WriteThroughput) || 0);
|
250
|
-
},
|
251
|
-
0,
|
252
|
-
);
|
253
|
-
const mediaType = group.VDisks?.reduce((type, vdisk) => {
|
254
|
-
const currentType = getPDiskType(vdisk.PDisk || {});
|
255
|
-
return currentType && (currentType === type || type === '')
|
256
|
-
? currentType
|
257
|
-
: 'Mixed';
|
258
|
-
}, '');
|
259
|
-
|
260
|
-
// VDisk doesn't have its own StoragePoolName when located inside StoragePool data
|
261
|
-
const vDisks = group.VDisks?.map((vdisk) => ({
|
262
|
-
...vdisk,
|
263
|
-
StoragePoolName: pool.Name,
|
264
|
-
Donors: vdisk.Donors?.map((donor) => ({
|
265
|
-
...donor,
|
266
|
-
StoragePoolName: pool.Name,
|
267
|
-
})),
|
268
|
-
}));
|
269
|
-
|
270
|
-
return [
|
271
|
-
...acc,
|
272
|
-
{
|
273
|
-
...group,
|
274
|
-
VDisks: vDisks,
|
275
|
-
Read: readSpeedBytesPerSec,
|
276
|
-
Write: writeSpeedBytesPerSec,
|
277
|
-
PoolName: pool.Name,
|
278
|
-
Used: usedSpaceBytes,
|
279
|
-
Limit: limitSizeBytes,
|
280
|
-
Missing: missing,
|
281
|
-
UsedSpaceFlag,
|
282
|
-
Type: mediaType || null,
|
283
|
-
},
|
284
|
-
];
|
285
|
-
},
|
286
|
-
[],
|
287
|
-
);
|
288
|
-
return [...acc, ...groups];
|
289
|
-
},
|
290
|
-
[],
|
291
|
-
);
|
292
|
-
});
|
293
|
-
|
294
|
-
export const getFlatListStorageNodes = createSelector([getStorageNodes], (storageNodes) => {
|
295
|
-
return _.map(storageNodes, (node) => {
|
296
|
-
const systemState = node.SystemState ?? {};
|
297
|
-
const missing = _.filter(node.PDisks, (p) => {
|
298
|
-
return p.State !== 'Normal';
|
299
|
-
}).length;
|
300
|
-
return {
|
301
|
-
NodeId: node.NodeId,
|
302
|
-
SystemState: systemState.SystemState,
|
303
|
-
DataCenter: systemState.DataCenter,
|
304
|
-
Rack: systemState.Rack,
|
305
|
-
Host: systemState.Host,
|
306
|
-
Endpoints: systemState.Endpoints,
|
307
|
-
uptime: calcUptime(systemState.StartTime),
|
308
|
-
StartTime: systemState.StartTime,
|
309
|
-
PDisks: node.PDisks,
|
310
|
-
Missing: missing,
|
311
|
-
};
|
312
|
-
});
|
313
|
-
});
|
314
|
-
|
315
|
-
export const getVDisksForPDisk = createSelector(
|
316
|
-
getStorageNodes,
|
317
|
-
(_state, nodeId) => nodeId,
|
318
|
-
(_state, _nodeId, pdiskId) => pdiskId,
|
319
|
-
(storageNodes, nodeId, pdiskId) => {
|
320
|
-
const targetNode = storageNodes?.find((node) => node.NodeId === nodeId);
|
321
|
-
return targetNode?.VDisks?.filter((vdisk) => vdisk.PDiskId === pdiskId).map((data) => ({
|
322
|
-
...data,
|
323
|
-
NodeId: nodeId,
|
324
|
-
}));
|
325
|
-
},
|
326
|
-
);
|
327
|
-
|
328
|
-
export const getFlatListStorage = createSelector(
|
329
|
-
[getStorageType, getFlatListStorageGroups, getFlatListStorageNodes],
|
330
|
-
(storageType, groupsList, nodesList) => {
|
331
|
-
if (storageType === STORAGE_TYPES.groups) {
|
332
|
-
return groupsList;
|
333
|
-
}
|
334
|
-
return nodesList;
|
335
|
-
},
|
336
|
-
);
|
337
|
-
|
338
|
-
const filterByText = (entities, type, text) => {
|
339
|
-
const cleanedFilter = text.trim().toLowerCase();
|
340
|
-
|
341
|
-
if (!cleanedFilter) {
|
342
|
-
return entities;
|
343
|
-
}
|
344
|
-
|
345
|
-
return entities.filter((entity) => {
|
346
|
-
if (type === STORAGE_TYPES.groups) {
|
347
|
-
return (
|
348
|
-
entity.PoolName.toLowerCase().includes(cleanedFilter) ||
|
349
|
-
entity.GroupID?.toString().includes(cleanedFilter)
|
350
|
-
);
|
351
|
-
}
|
352
|
-
|
353
|
-
return (
|
354
|
-
entity.NodeId.toString().includes(cleanedFilter) ||
|
355
|
-
entity.Host.toLowerCase().includes(cleanedFilter)
|
356
|
-
);
|
357
|
-
});
|
358
|
-
};
|
359
|
-
|
360
|
-
const filterByUsage = (entities, usage) => {
|
361
|
-
if (!Array.isArray(usage) || usage.length === 0) {
|
362
|
-
return entities;
|
363
|
-
}
|
364
|
-
|
365
|
-
return entities.filter((entity) => {
|
366
|
-
const entityUsage = getUsage(entity, 5);
|
367
|
-
return usage.some((val) => Number(val) <= entityUsage && entityUsage < Number(val) + 5);
|
368
|
-
});
|
369
|
-
};
|
370
|
-
|
371
|
-
export const getFilteredEntities = createSelector(
|
372
|
-
[getStorageFilter, getUsageFilter, getStorageType, getNodesUptimeFilter, getFlatListStorage],
|
373
|
-
(textFilter, usageFilter, type, nodesUptimeFilter, entities) => {
|
374
|
-
let result = entities;
|
375
|
-
result = filterByText(result, type, textFilter);
|
376
|
-
result = filterByUsage(result, usageFilter);
|
377
|
-
|
378
|
-
if (type === STORAGE_TYPES.nodes) {
|
379
|
-
result = filterNodesByUptime(result, nodesUptimeFilter);
|
380
|
-
}
|
381
|
-
|
382
|
-
return result;
|
383
|
-
},
|
384
|
-
);
|
385
|
-
|
386
|
-
export const getUsageFilterOptions = createSelector(getFlatListStorage, (entities) => {
|
387
|
-
const items = {};
|
388
|
-
|
389
|
-
entities.forEach((entity) => {
|
390
|
-
const usage = getUsage(entity, 5);
|
391
|
-
|
392
|
-
if (!Object.prototype.hasOwnProperty.call(items, usage)) {
|
393
|
-
items[usage] = 0;
|
394
|
-
}
|
395
|
-
|
396
|
-
items[usage] += 1;
|
397
|
-
});
|
398
|
-
|
399
|
-
return Object.entries(items)
|
400
|
-
.map(([threshold, count]) => ({threshold, count}))
|
401
|
-
.sort((a, b) => b.threshold - a.threshold);
|
402
|
-
});
|
403
|
-
|
404
|
-
export default storage;
|
@@ -1,57 +0,0 @@
|
|
1
|
-
import {GIGABYTE, KILOBYTE, MEGABYTE} from '../constants';
|
2
|
-
import {isNumeric} from '../utils';
|
3
|
-
|
4
|
-
import i18n from './i18n';
|
5
|
-
|
6
|
-
const sizes = {
|
7
|
-
b: {
|
8
|
-
value: 1,
|
9
|
-
label: i18n('b'),
|
10
|
-
},
|
11
|
-
kb: {
|
12
|
-
value: KILOBYTE,
|
13
|
-
label: i18n('kb'),
|
14
|
-
},
|
15
|
-
mb: {
|
16
|
-
value: MEGABYTE,
|
17
|
-
label: i18n('mb'),
|
18
|
-
},
|
19
|
-
gb: {
|
20
|
-
value: GIGABYTE,
|
21
|
-
label: i18n('gb'),
|
22
|
-
},
|
23
|
-
};
|
24
|
-
|
25
|
-
export type IBytesSizes = keyof typeof sizes;
|
26
|
-
|
27
|
-
interface FormatBytesArgs {
|
28
|
-
value: number | string | undefined;
|
29
|
-
size?: IBytesSizes;
|
30
|
-
precision?: number;
|
31
|
-
withLabel?: boolean;
|
32
|
-
isSpeed?: boolean;
|
33
|
-
}
|
34
|
-
|
35
|
-
export const formatBytesCustom = ({
|
36
|
-
value,
|
37
|
-
size = 'mb',
|
38
|
-
precision = 0,
|
39
|
-
withLabel = true,
|
40
|
-
isSpeed = false,
|
41
|
-
}: FormatBytesArgs) => {
|
42
|
-
if (!isNumeric(value)) {
|
43
|
-
return '';
|
44
|
-
}
|
45
|
-
|
46
|
-
let result = (Number(value) / sizes[size].value).toFixed(precision);
|
47
|
-
|
48
|
-
if (withLabel) {
|
49
|
-
result += ` ${sizes[size].label}`;
|
50
|
-
|
51
|
-
if (isSpeed) {
|
52
|
-
result += i18n('perSecond');
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
return result;
|
57
|
-
};
|