mirror of https://github.com/rancher/ui.git
647 lines
14 KiB
JavaScript
647 lines
14 KiB
JavaScript
// Note: nothing Ember specific should go in here...
|
|
//
|
|
import { formatSi } from './parse-unit';
|
|
import { isEmpty } from '@ember/utils';
|
|
import { isArray } from '@ember/array';
|
|
import ipaddr from 'ipaddr.js';
|
|
import { decamelize } from '@ember/string';
|
|
|
|
export function asciiLike(str) {
|
|
str = str || '';
|
|
if ( str.match(/[^\r\n\t\x20-\x7F]/) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
export function arrayDiff(a, b) {
|
|
return a.filter((i) => {
|
|
return !b.includes(i);
|
|
});
|
|
}
|
|
|
|
export function arrayIntersect(a, b) {
|
|
return a.filter((i) => {
|
|
return b.includes(i);
|
|
});
|
|
}
|
|
|
|
export function compareDisplayEndpoint(i1, i2) {
|
|
if ( !i1 ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( !i2 ) {
|
|
return -1;
|
|
}
|
|
|
|
const in1 = i1.displayEndpoint;
|
|
const in2 = i2.displayEndpoint;
|
|
|
|
if ( !in1 ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( !in2 ) {
|
|
return -1;
|
|
}
|
|
|
|
if ( in1.startsWith('/') && !in2.startsWith('/') ) {
|
|
return -1;
|
|
} else if ( !in1.startsWith('/') && in2.startsWith('/') ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( in1 === '80/http' && in2 !== '80/http' ) {
|
|
return -1;
|
|
} else if ( in1 !== '80/http' && in2 === '80/http' ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( in1 === '443/https' && in2 !== '443/https' ) {
|
|
return -1;
|
|
} else if ( in1 !== '443/https' && in2 === '443/https' ) {
|
|
return 1;
|
|
}
|
|
|
|
return in1.localeCompare(in2);
|
|
}
|
|
|
|
export function convertToMillis(strValue) {
|
|
if (!strValue) {
|
|
return '';
|
|
}
|
|
if (strValue.endsWith('m')) {
|
|
return parseInt(strValue.substr(0, strValue.length - 1), 10);
|
|
} else {
|
|
return parseInt(strValue, 10) * 1000;
|
|
}
|
|
}
|
|
|
|
export function filterByValues(ary, field, values) {
|
|
return ary.filter((obj) => {
|
|
return values.includes(obj.get(field));
|
|
});
|
|
}
|
|
|
|
export function download(url, id = '__downloadIframe') {
|
|
var iframe = document.getElementById(id);
|
|
|
|
if ( !iframe ) {
|
|
iframe = document.createElement('iframe');
|
|
iframe.style.display = 'none';
|
|
iframe.id = id;
|
|
document.body.appendChild(iframe);
|
|
}
|
|
|
|
iframe.src = url;
|
|
}
|
|
|
|
export function popupWindowOptions(width, height) {
|
|
var s = window.screen;
|
|
var opt = {
|
|
width: Math.min(s.width, width || 1040),
|
|
height: Math.min(s.height, height || 768),
|
|
resizable: 1,
|
|
scrollbars: 1,
|
|
};
|
|
|
|
opt.left = Math.max(0, (s.width - opt.width) / 2);
|
|
opt.top = Math.max(0, (s.height - opt.height) / 2);
|
|
|
|
var optStr = Object.keys(opt).map((k) => {
|
|
return `${ k }=${ opt[k] }`;
|
|
}).join(',');
|
|
|
|
return optStr;
|
|
}
|
|
|
|
var entityMap = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": ''',
|
|
'/': '/'
|
|
};
|
|
|
|
export function escapeHtml(html) {
|
|
return String(html).replace(/[&<>"'\/]/g, (s) => {
|
|
return entityMap[s];
|
|
});
|
|
}
|
|
|
|
export function escapeRegex(string){
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
}
|
|
|
|
export function addQueryParam(url, key, val) {
|
|
let out = url + (url.indexOf('?') >= 0 ? '&' : '?');
|
|
|
|
// val can be a string or an array of strings
|
|
if ( !Array.isArray(val) ) {
|
|
val = [val];
|
|
}
|
|
out += val.map((v) => {
|
|
return `${ encodeURIComponent(key) }=${ encodeURIComponent(v) }`;
|
|
}).join('&');
|
|
|
|
return out;
|
|
}
|
|
|
|
export function addQueryParams(url, params) {
|
|
if ( params && typeof params === 'object' ) {
|
|
Object.keys(params).forEach((key) => {
|
|
url = addQueryParam(url, key, params[key]);
|
|
});
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
export function parseUrl(url) {
|
|
var a = document.createElement('a');
|
|
|
|
a.href = url;
|
|
|
|
return a.cloneNode(false);
|
|
}
|
|
|
|
export function absoluteUrl(url) {
|
|
return parseUrl(url).href;
|
|
}
|
|
|
|
export function addAuthorization(url, user, pass) {
|
|
url = absoluteUrl(url);
|
|
var pos = url.indexOf('//');
|
|
|
|
if ( pos >= 0 ) {
|
|
url = `${ url.substr(0, pos + 2) +
|
|
(user ? encodeURIComponent(user) : '') +
|
|
(pass ? `:${ encodeURIComponent(pass) }` : '')
|
|
}@${ url.substr(pos + 2) }`;
|
|
} else {
|
|
throw new Error(`That doesn't look like a URL: ${ url }`);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
export function ucFirst(str) {
|
|
str = str || '';
|
|
|
|
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
|
}
|
|
|
|
export function lcFirst(str) {
|
|
str = str || '';
|
|
|
|
return str.substr(0, 1).toLowerCase() + str.substr(1);
|
|
}
|
|
|
|
export function strPad(str, toLength, padChars, right) {
|
|
str = `${ str }`;
|
|
padChars = padChars || ' ';
|
|
|
|
if ( str.length >= toLength ) {
|
|
return str;
|
|
}
|
|
|
|
var neededLen = toLength - str.length + 1;
|
|
var padStr = (new Array(neededLen)).join(padChars);
|
|
|
|
padStr = padStr.substr(0, neededLen);
|
|
|
|
if ( right ) {
|
|
return str + padStr;
|
|
} else {
|
|
return padStr + str;
|
|
}
|
|
}
|
|
|
|
// Turn thing1 into thing00000001 so that the numbers sort numerically
|
|
export function sortableNumericSuffix(str) {
|
|
str = str || '';
|
|
let match = str.match(/^(.*[^0-9])([0-9]+)$/)
|
|
|
|
if ( match ) {
|
|
return match[1] + Util.strPad(match[2], 8, '0');
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
export function timerFuzz(ms, maxFuzz = 0.1) {
|
|
var factor = Math.random() * 2 * maxFuzz + (1 - maxFuzz);
|
|
|
|
return Math.max(1, ms * factor);
|
|
}
|
|
|
|
export function random32(count) {
|
|
count = Math.max(0, count || 1);
|
|
var out = [];
|
|
var i;
|
|
|
|
if ( window.crypto && window.crypto.getRandomValues ) {
|
|
var tmp = new Uint32Array(count);
|
|
|
|
window.crypto.getRandomValues(tmp);
|
|
for ( i = 0 ; i < tmp.length ; i++ ) {
|
|
out[i] = tmp[i];
|
|
}
|
|
} else {
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
out[i] = Math.random() * 4294967296; // Math.pow(2,32);
|
|
}
|
|
}
|
|
|
|
if ( count === 1 ) {
|
|
return out[0];
|
|
} else {
|
|
return out;
|
|
}
|
|
}
|
|
|
|
const alpha = 'abcdefghijklmnopqrstuvwxyz';
|
|
const num = '0123456789';
|
|
const sym = '!@#$%^&*()_+-=[]{};:,./<>?|';
|
|
const randomCharsets = {
|
|
numeric: num,
|
|
novowels: 'bcdfghjklmnpqrstvwxz2456789',
|
|
loweralpha: alpha,
|
|
upperalpha: alpha.toUpperCase(),
|
|
hex: `${ num }ABCDEF`,
|
|
alpha: alpha + alpha.toUpperCase(),
|
|
alphanum: alpha + alpha.toUpperCase() + num,
|
|
password: alpha + alpha.toUpperCase() + num + alpha + alpha.toUpperCase() + num + sym,
|
|
// ^-- includes alpha / ALPHA / num twice to reduce the occurrence of symbols
|
|
};
|
|
|
|
function getRandomInt(min, max) {
|
|
min = Math.ceil(min);
|
|
max = Math.floor(max);
|
|
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
}
|
|
|
|
function getCharset(str) {
|
|
let out = '';
|
|
|
|
for (let i = 0; i < str.length; i++){
|
|
const current = str.charAt(i);
|
|
|
|
if ( current !== '-' ) {
|
|
out += current;
|
|
} else {
|
|
const start = str.charAt(i - 1);
|
|
const end = str.charAt(i + 1);
|
|
const alphanum = randomCharsets.alphanum;
|
|
|
|
out += alphanum.substr(alphanum.indexOf(start) + 1, alphanum.indexOf(end));
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export function randomStr(minLength = 16, maxLength = 16, charset = 'alphanum') {
|
|
var chars = randomCharsets[charset];
|
|
|
|
if ( !chars ) {
|
|
if ( charset ) {
|
|
chars = getCharset(charset);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
var charCount = chars.length;
|
|
|
|
return random32(getRandomInt(minLength, maxLength)).map((val) => {
|
|
return chars[ val % charCount ];
|
|
}).join('');
|
|
}
|
|
|
|
export function formatPercent(value, maxPrecision = 2) {
|
|
if ( value < 1 && maxPrecision >= 2 ) {
|
|
return `${ Math.round(value * 100) / 100 }%`;
|
|
} else if ( value < 10 && maxPrecision >= 1 ) {
|
|
return `${ Math.round(value * 10) / 10 }%`;
|
|
} else {
|
|
return `${ Math.round(value) }%`;
|
|
}
|
|
}
|
|
|
|
export function formatMib(value) {
|
|
return formatSi(value, 1024, 'iB', 'B', 2);
|
|
}
|
|
|
|
export function formatSecond(value) {
|
|
if ( value < 0.1 ) {
|
|
return `${ roundValue(value * 1000) } ms`
|
|
} else {
|
|
return `${ roundValue(value) } s`
|
|
}
|
|
}
|
|
|
|
export function formatKbps(value) {
|
|
return formatSi(value, 1000, 'bps', 'Bps', 1);
|
|
}
|
|
|
|
export function roundValue(value) {
|
|
if ( value < 1 ) {
|
|
return Math.round(value * 100) / 100;
|
|
} else if ( value < 10 ) {
|
|
return Math.round(value * 10) / 10;
|
|
} else {
|
|
return Math.round(value);
|
|
}
|
|
}
|
|
|
|
export function formatGB(inMB) {
|
|
return formatSi(inMB, 1000, 'B', 'B', 2);
|
|
}
|
|
|
|
export function constructUrl(ssl, host, port) {
|
|
var out = `http${ ssl ? 's' : '' }://${ host }`;
|
|
|
|
if ( (ssl && port !== 443) || (!ssl && port !== 80) ) {
|
|
out += `:${ port }`;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export function pluralize(count, singular, plural) {
|
|
if ( !plural ) {
|
|
if ( singular.substr(-1, 1) === 's' ) {
|
|
plural = `${ singular }es`;
|
|
} else {
|
|
plural = `${ singular }s`;
|
|
}
|
|
}
|
|
|
|
if ( count === 1 ) {
|
|
return `${ count } ${ singular }`;
|
|
} else {
|
|
return `${ count } ${ plural }`;
|
|
}
|
|
}
|
|
|
|
export function uniqKeys(data, field = undefined) {
|
|
// Make a map of all the unique category names.
|
|
// If multiple casings of the same name are present, first wins.
|
|
let cased = {};
|
|
|
|
data.map((obj) => (field ? obj[field] : obj))
|
|
.filter((str) => str && str.length)
|
|
.forEach((str) => {
|
|
let lc = str.toLowerCase();
|
|
|
|
if ( !cased[lc] ) {
|
|
cased[lc] = str;
|
|
}
|
|
});
|
|
|
|
return Object.keys(cased).uniq().sort().map((str) => cased[str]);
|
|
}
|
|
|
|
export function camelToTitle(str) {
|
|
return (str || '').dasherize().split('-').map((str) => {
|
|
return ucFirst(str);
|
|
}).join(' ');
|
|
}
|
|
|
|
export function toTitle(str) {
|
|
return str
|
|
.split(' ')
|
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
|
|
export function isNumeric(str) {
|
|
return (`${ str }`).match(/^([0-9]+\.)?[0-9]*$/);
|
|
}
|
|
|
|
export function hostname(str) {
|
|
return (str || '').trim().replace(/^[a-z0-9]+:\/+/i, '').replace(/\/.*$/g, '');
|
|
}
|
|
export function isPrivate(name) {
|
|
return ipaddr.parse(name).range() === 'private' ? true : false;
|
|
}
|
|
|
|
export function stripScheme(url) {
|
|
let val = (url || '').trim();
|
|
|
|
return val.replace(/^https?:\/\//, '');
|
|
}
|
|
|
|
export function isBadTld(name) {
|
|
if ( hostname(name).match(/\.local$/) ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function keyNotType(k) {
|
|
return Object.keys(k).filter((key) => key !== 'type').length > 0;
|
|
}
|
|
|
|
/**
|
|
* Removes properties from obj that are empty.
|
|
* If a property is a part of excludedKeys it will be inluded regardless of whether or not it's empty.
|
|
* If a property is a part of excludedChildrenKeys it will exclude the property if it's empty but won't do the empty check on any of it's children.
|
|
*/
|
|
export function removeEmpty(obj, excludedKeys = [], excludedChildrenKeys = []){
|
|
return Object.keys(obj)
|
|
.filter((k) => {
|
|
if (excludedKeys.indexOf(k) >= 0 || !isEmpty(obj[k]) && (typeof obj[k] !== 'object' || keyNotType(obj[k]))) {
|
|
return true;
|
|
}
|
|
})
|
|
.reduce((newObj, k) => !isArray(obj[k]) && typeof obj[k] === 'object' && excludedKeys.indexOf(k) === -1 ?
|
|
Object.assign(newObj, { [k]: excludedChildrenKeys.includes(k) ? obj[k] : removeEmpty(obj[k], excludedKeys, excludedChildrenKeys) }) :
|
|
Object.assign(newObj, { [k]: obj[k] }),
|
|
{});
|
|
}
|
|
|
|
export function underlineToCamel(str) {
|
|
return (str || '').split('_').map((t, i) => {
|
|
if ( i === 0 ) {
|
|
return t;
|
|
} else {
|
|
return t === 'qps' ? 'QPS' : t.capitalize();
|
|
}
|
|
}).join('');
|
|
}
|
|
|
|
export function keysToDecamelize(obj, ignore = [], drop = []) {
|
|
if ( !typeof (obj) === 'object' || typeof (obj) === 'string' || typeof (obj) === 'number' || typeof (obj) === 'boolean' || isEmpty(obj) ) {
|
|
return obj;
|
|
}
|
|
|
|
const keys = Object.keys(obj);
|
|
let n = keys.length;
|
|
|
|
while (n--) {
|
|
const key = keys[n];
|
|
const titleKey = decamelize(key);
|
|
|
|
if ( (ignore || []).includes(key) || drop.includes(key) ) {
|
|
if (drop.includes(key)) {
|
|
delete obj[key];
|
|
continue;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
obj[titleKey] = keysToDecamelize(obj[key], ignore, drop);
|
|
|
|
if ( key !== titleKey ) {
|
|
delete obj[key];
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
const UNDER_SCORE_PARAMS = ['.rancher_kubernetes_engine_config.network.options', '.rancher_kubernetes_engine_config.dns.options'];
|
|
const KUBE_SERVICES = ['.rancher_kubernetes_engine_config.services'];
|
|
|
|
export function keysToCamel(obj, path = '') {
|
|
if ( !typeof (obj) === 'object' || typeof (obj) === 'string' || typeof (obj) === 'number' || typeof (obj) === 'boolean' || isEmpty(obj) ) {
|
|
return obj;
|
|
}
|
|
const keys = Object.keys(obj);
|
|
let n = keys.length;
|
|
|
|
while (n--) {
|
|
const key = keys[n];
|
|
|
|
let titleKey = key;
|
|
|
|
if ( KUBE_SERVICES.indexOf(path) > -1 ) {
|
|
titleKey = underlineToCamel(key.replace(/-/g, '_'));
|
|
} else if ( UNDER_SCORE_PARAMS.indexOf(path) === -1 ) {
|
|
titleKey = underlineToCamel(key);
|
|
}
|
|
|
|
obj[titleKey] = keysToCamel(obj[key], `${ path }.${ key }`);
|
|
if ( key !== titleKey ) {
|
|
delete obj[key];
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
export function deepCopy(obj) {
|
|
return JSON.parse(JSON.stringify(obj));
|
|
}
|
|
|
|
export function validateEndpoint(str) {
|
|
// credit to https://stackoverflow.com/questions/4460586/javascript-regular-expression-to-check-for-ip-addresses
|
|
return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(str);
|
|
}
|
|
|
|
export function validateCertWeakly(certs) {
|
|
const BEGIN_CERTIFICATE = [
|
|
'-----BEGIN CERTIFICATE-----'
|
|
];
|
|
|
|
return certs.trim().startsWith(BEGIN_CERTIFICATE[0]);
|
|
}
|
|
|
|
export function validateKeyWeakly(key) {
|
|
let valid = false;
|
|
const BEGIN_KEY = [
|
|
'-----BEGIN PRIVATE KEY-----',
|
|
'-----BEGIN EC PRIVATE KEY-----',
|
|
'-----BEGIN RSA PRIVATE KEY-----',
|
|
]
|
|
|
|
BEGIN_KEY.forEach((prefix) => {
|
|
if ( key.trim().startsWith(prefix) ) {
|
|
valid = true;
|
|
}
|
|
});
|
|
|
|
return valid;
|
|
}
|
|
|
|
export function requiredError(label, payload = {}) {
|
|
const intl = window.l('service:intl')
|
|
|
|
return `"${ intl.t(label, payload) }" ${ intl.t('generic.isRequired') }`
|
|
}
|
|
|
|
export function parseCamelcase(str = '') {
|
|
return ucFirst(str).replace(/([A-Z]+)*([A-Z][a-z])/g, '$1 $2')
|
|
}
|
|
|
|
export function extractUniqueStrings(strings) {
|
|
const index = strings.reduce((agg, s) => ({
|
|
...agg,
|
|
[s]: true
|
|
}), {});
|
|
|
|
return Object.keys(index);
|
|
}
|
|
|
|
var Util = {
|
|
asciiLike,
|
|
absoluteUrl,
|
|
addAuthorization,
|
|
addQueryParam,
|
|
addQueryParams,
|
|
arrayDiff,
|
|
arrayIntersect,
|
|
compareDisplayEndpoint,
|
|
camelToTitle,
|
|
convertToMillis,
|
|
constructUrl,
|
|
download,
|
|
deepCopy,
|
|
escapeHtml,
|
|
escapeRegex,
|
|
filterByValues,
|
|
formatGB,
|
|
formatKbps,
|
|
formatMib,
|
|
formatSecond,
|
|
formatPercent,
|
|
hostname,
|
|
isBadTld,
|
|
isNumeric,
|
|
isPrivate,
|
|
keysToCamel,
|
|
keysToDecamelize,
|
|
lcFirst,
|
|
parseUrl,
|
|
pluralize,
|
|
popupWindowOptions,
|
|
random32,
|
|
randomStr,
|
|
removeEmpty,
|
|
roundValue,
|
|
sortableNumericSuffix,
|
|
strPad,
|
|
stripScheme,
|
|
timerFuzz,
|
|
toTitle,
|
|
ucFirst,
|
|
uniqKeys,
|
|
underlineToCamel,
|
|
validateEndpoint,
|
|
requiredError,
|
|
parseCamelcase,
|
|
extractUniqueStrings
|
|
};
|
|
|
|
window.Util = Util;
|
|
export default Util;
|