// 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;