linkerd2/web/app/js/components/util/Utils.js

247 lines
6.2 KiB
JavaScript

import _has from 'lodash/has';
import _isNil from 'lodash/isNil';
import _lowerCase from 'lodash/lowerCase';
import _startCase from 'lodash/startCase';
import { format as d3Format } from 'd3-format';
/*
* Display grid constants
*/
export const baseWidth = 8; // design base width of 8px
export const rowGutter = 3 * baseWidth;
/*
* Number formatters
*/
const successRateFormatter = d3Format('.2%');
const commaFormatter = d3Format(',');
const secondsFormatter = d3Format(',.3s');
export const formatWithComma = m => {
if (_isNil(m)) {
return '---';
} else {
return commaFormatter(m);
}
};
const niceLatency = l => commaFormatter(Math.round(l));
export const formatLatencySec = latency => {
const s = parseFloat(latency);
if (_isNil(s)) {
return '---';
} else if (s === parseFloat(0.0)) {
return '0 s';
} else if (s < 0.001) {
return `${niceLatency(s * 1000 * 1000)} µs`;
} else if (s < 1.0) {
return `${niceLatency(s * 1000)} ms`;
} else {
return `${secondsFormatter(s)} s`;
}
};
export const formatLatencyMs = m => {
if (_isNil(m)) {
return '---';
} else {
return `${formatLatencySec(m / 1000)}`;
}
};
/*
* Add commas to a number (converting it to a string in the process)
*/
export function addCommas(nStr) {
let nStr_ = nStr;
nStr_ += '';
const x = nStr_.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? `.${x[1]}` : '';
const rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1,$2');
}
return x1 + x2;
}
/*
* Round a number to a given number of decimals
*/
export const roundNumber = (num, dec) => {
return Math.round(num * 10 ** dec) / 10 ** dec;
};
/*
* Shorten and style number
*/
export const styleNum = (number, unit = '', truncate = true) => {
let number_ = number;
if (Number.isNaN(number_)) {
return 'N/A';
}
if (truncate && number_ > 999999999) {
number_ = roundNumber(number_ / 1000000000.0, 3);
return `${addCommas(number_)}G${unit}`;
} else if (truncate && number_ > 999999) {
number_ = roundNumber(number_ / 1000000.0, 3);
return `${addCommas(number_)}M${unit}`;
} else if (truncate && number_ > 999) {
number_ = roundNumber(number_ / 1000.0, 3);
return `${addCommas(number_)}k${unit}`;
} else if (number_ > 999) {
number_ = roundNumber(number_, 0);
return addCommas(number_) + unit;
} else {
number_ = roundNumber(number_, 2);
return addCommas(number_) + unit;
}
};
export const metricToFormatter = {
REQUEST_RATE: m => _isNil(m) ? '---' : styleNum(m, ' RPS', true),
SUCCESS_RATE: m => _isNil(m) ? '---' : successRateFormatter(m),
LATENCY: formatLatencyMs,
UNTRUNCATED: m => styleNum(m, '', false),
BYTES: m => _isNil(m) ? '---' : styleNum(m, 'B/s', true),
NO_UNIT: m => _isNil(m) ? '---' : styleNum(m, '', true),
};
/*
* Convert a string to a valid css class name
*/
export const toClassName = name => {
if (!name) { return ''; }
return _lowerCase(name).replace(/[^a-zA-Z0-9]/g, '_');
};
/*
Create regex string from user input for a filter
*/
export const regexFilterString = input => {
// make input lower case and strip out unwanted characters
const input_ = input.replace(/[^A-Z0-9/.\-_*]/gi, '').toLowerCase();
// replace "*" in input with wildcard
return new RegExp(input_.replace(/[*]/g, '.+'));
};
/*
Get a singular resource name from a plural resource
*/
export const singularResource = resource => {
if (resource === 'authorities') {
return 'authority';
} else { return resource.replace(/s$/, ''); }
};
/*
Nicely readable names for the stat resources
*/
export const friendlyTitle = singularOrPluralResource => {
const resource = singularResource(singularOrPluralResource);
let titleCase = _startCase(resource);
if (resource === 'replicationcontroller') {
titleCase = _startCase('replication controller');
} else if (resource === 'daemonset') {
titleCase = _startCase('daemon set');
} else if (resource === 'statefulset') {
titleCase = _startCase('stateful set');
} else if (resource === 'trafficsplit') {
titleCase = _startCase('traffic split');
} else if (resource === 'cronjob') {
titleCase = _startCase('cron job');
} else if (resource === 'replicaset') {
titleCase = _startCase('replica set');
}
const titles = { singular: titleCase };
if (resource === 'authority') {
titles.plural = 'Authorities';
} else {
titles.plural = `${titles.singular}s`;
}
return titles;
};
/*
Get the resource type from the /pods response, whose json
is camelCased.
*/
const camelCaseLookUp = {
replicaset: 'replicaSet',
replicationcontroller: 'replicationController',
statefulset: 'statefulSet',
trafficsplit: 'trafficSplit',
daemonset: 'daemonSet',
cronjob: 'cronJob',
};
export const resourceTypeToCamelCase = resource => camelCaseLookUp[resource] || resource;
/*
A simplified version of ShortNameFromCanonicalResourceName
*/
export const shortNameLookup = {
deployment: 'deploy',
daemonset: 'ds',
namespace: 'ns',
pod: 'po',
replicationcontroller: 'rc',
replicaset: 'rs',
service: 'svc',
statefulset: 'sts',
trafficsplit: 'ts',
job: 'job',
authority: 'au',
cronjob: 'cj',
};
export const podOwnerLookup = {
deployment: 'deploy',
daemonset: 'ds',
replicationcontroller: 'rc',
replicaset: 'rs',
statefulset: 'sts',
cronjob: 'cj',
};
export const toShortResourceName = name => shortNameLookup[name] || name;
export const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;
export const isResource = name => {
const singularResourceName = singularResource(name);
return _has(shortNameLookup, singularResourceName);
};
/*
produce octets given an ip address
*/
const decodeIPToOctets = ip => {
const ip_ = parseInt(ip, 10);
return [
(ip_ >> 24) & 255,
(ip_ >> 16) & 255,
(ip_ >> 8) & 255,
ip_ & 255,
];
};
/*
converts an address to an ipv4 formatted host:port pair
*/
export const publicAddressToString = (ipv4, port) => {
const octets = decodeIPToOctets(ipv4);
return `${octets.join('.')}:${port}`;
};
export const getSrClassification = sr => {
if (sr < 0.9) {
return 'status-poor';
} else if (sr < 0.95) {
return 'status-ok';
} else { return 'status-good'; }
};