github.com/minio/console@v1.4.1/web-app/src/common/utils.ts (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import storage from "local-storage-fallback"; 18 import { IBytesCalc, IErasureCodeCalc, IStorageFactors } from "./types"; 19 20 import get from "lodash/get"; 21 22 const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes 23 24 export const units = [ 25 "B", 26 "KiB", 27 "MiB", 28 "GiB", 29 "TiB", 30 "PiB", 31 "EiB", 32 "ZiB", 33 "YiB", 34 ]; 35 export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"]; 36 export const k8sCalcUnits = ["B", ...k8sUnits]; 37 38 export const niceBytes = (x: string, showK8sUnits: boolean = false) => { 39 let n = parseInt(x, 10) || 0; 40 41 return niceBytesInt(n, showK8sUnits); 42 }; 43 44 export const niceBytesInt = (n: number, showK8sUnits: boolean = false) => { 45 let l = 0; 46 47 while (n >= 1024 && ++l) { 48 n = n / 1024; 49 } 50 // include a decimal point and a tenths-place digit if presenting 51 // less than ten of KB or greater units 52 const k8sUnitsN = ["B", ...k8sUnits]; 53 return n.toFixed(1) + " " + (showK8sUnits ? k8sUnitsN[l] : units[l]); 54 }; 55 56 export const deleteCookie = (name: string) => { 57 document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; 58 }; 59 60 export const clearSession = () => { 61 storage.removeItem("token"); 62 storage.removeItem("auth-state"); 63 deleteCookie("token"); 64 deleteCookie("idp-refresh-token"); 65 }; 66 67 // timeFromDate gets time string from date input 68 export const timeFromDate = (d: Date) => { 69 let h = d.getHours() < 10 ? `0${d.getHours()}` : `${d.getHours()}`; 70 let m = d.getMinutes() < 10 ? `0${d.getMinutes()}` : `${d.getMinutes()}`; 71 let s = d.getSeconds() < 10 ? `0${d.getSeconds()}` : `${d.getSeconds()}`; 72 73 return `${h}:${m}:${s}:${d.getMilliseconds()}`; 74 }; 75 76 // units to be used in a dropdown 77 export const k8sScalarUnitsExcluding = (exclude?: string[]) => { 78 return k8sUnits 79 .filter((unit) => { 80 if (exclude && exclude.includes(unit)) { 81 return false; 82 } 83 return true; 84 }) 85 .map((unit) => { 86 return { label: unit, value: unit }; 87 }); 88 }; 89 90 //getBytes, converts from a value and a unit from units array to bytes as a string 91 export const getBytes = ( 92 value: string, 93 unit: string, 94 fromk8s: boolean = false, 95 ): string => { 96 return getBytesNumber(value, unit, fromk8s).toString(10); 97 }; 98 99 //getBytesNumber, converts from a value and a unit from units array to bytes 100 export const getBytesNumber = ( 101 value: string, 102 unit: string, 103 fromk8s: boolean = false, 104 ): number => { 105 const vl: number = parseFloat(value); 106 107 const unitsTake = fromk8s ? k8sCalcUnits : units; 108 109 const powFactor = unitsTake.findIndex((element) => element === unit); 110 111 if (powFactor === -1) { 112 return 0; 113 } 114 const factor = Math.pow(1024, powFactor); 115 const total = vl * factor; 116 117 return total; 118 }; 119 120 export const setMemoryResource = ( 121 memorySize: number, 122 capacitySize: string, 123 maxMemorySize: number, 124 ) => { 125 // value always comes as Gi 126 const requestedSizeBytes = getBytes(memorySize.toString(10), "Gi", true); 127 const memReqSize = parseInt(requestedSizeBytes, 10); 128 if (maxMemorySize === 0) { 129 return { 130 error: "There is no memory available for the selected number of nodes", 131 request: 0, 132 limit: 0, 133 }; 134 } 135 136 if (maxMemorySize < minMemReq) { 137 return { 138 error: "There are not enough memory resources available", 139 request: 0, 140 limit: 0, 141 }; 142 } 143 144 if (memReqSize < minMemReq) { 145 return { 146 error: "The requested memory size must be greater than 2Gi", 147 request: 0, 148 limit: 0, 149 }; 150 } 151 if (memReqSize > maxMemorySize) { 152 return { 153 error: 154 "The requested memory is greater than the max available memory for the selected number of nodes", 155 request: 0, 156 limit: 0, 157 }; 158 } 159 160 const capSize = parseInt(capacitySize, 10); 161 let memLimitSize = memReqSize; 162 // set memory limit based on the capacitySize 163 // if capacity size is lower than 1TiB we use the limit equal to request 164 if (capSize >= parseInt(getBytes("1", "Pi", true), 10)) { 165 memLimitSize = Math.max( 166 memReqSize, 167 parseInt(getBytes("64", "Gi", true), 10), 168 ); 169 } else if (capSize >= parseInt(getBytes("100", "Ti"), 10)) { 170 memLimitSize = Math.max( 171 memReqSize, 172 parseInt(getBytes("32", "Gi", true), 10), 173 ); 174 } else if (capSize >= parseInt(getBytes("10", "Ti"), 10)) { 175 memLimitSize = Math.max( 176 memReqSize, 177 parseInt(getBytes("16", "Gi", true), 10), 178 ); 179 } else if (capSize >= parseInt(getBytes("1", "Ti"), 10)) { 180 memLimitSize = Math.max( 181 memReqSize, 182 parseInt(getBytes("8", "Gi", true), 10), 183 ); 184 } 185 186 return { 187 error: "", 188 request: memReqSize, 189 limit: memLimitSize, 190 }; 191 }; 192 193 // Erasure Code Parity Calc 194 export const erasureCodeCalc = ( 195 parityValidValues: string[], 196 totalDisks: number, 197 pvSize: number, 198 totalNodes: number, 199 ): IErasureCodeCalc => { 200 // Parity Values is empty 201 if (parityValidValues.length < 1) { 202 return { 203 error: 1, 204 defaultEC: "", 205 erasureCodeSet: 0, 206 maxEC: "", 207 rawCapacity: "0", 208 storageFactors: [], 209 }; 210 } 211 212 const totalStorage = totalDisks * pvSize; 213 const maxEC = parityValidValues[0]; 214 const maxParityNumber = parseInt(maxEC.split(":")[1], 10); 215 216 const erasureStripeSet = maxParityNumber * 2; // ESS is calculated by multiplying maximum parity by two. 217 218 const storageFactors: IStorageFactors[] = parityValidValues.map( 219 (currentParity) => { 220 const parityNumber = parseInt(currentParity.split(":")[1], 10); 221 const storageFactor = 222 erasureStripeSet / (erasureStripeSet - parityNumber); 223 224 const maxCapacity = Math.floor(totalStorage / storageFactor); 225 const maxTolerations = 226 totalDisks - Math.floor(totalDisks / storageFactor); 227 return { 228 erasureCode: currentParity, 229 storageFactor, 230 maxCapacity: maxCapacity.toString(10), 231 maxFailureTolerations: maxTolerations, 232 }; 233 }, 234 ); 235 236 let defaultEC = maxEC; 237 238 const fourVar = parityValidValues.find((element) => element === "EC:4"); 239 240 if (fourVar) { 241 defaultEC = "EC:4"; 242 } 243 244 return { 245 error: 0, 246 storageFactors, 247 maxEC, 248 rawCapacity: totalStorage.toString(10), 249 erasureCodeSet: erasureStripeSet, 250 defaultEC, 251 }; 252 }; 253 254 // 92400 seconds -> 1 day, 1 hour, 40 minutes. 255 export const niceTimeFromSeconds = (seconds: number): string => { 256 const days = Math.floor(seconds / (3600 * 24)); 257 const hours = Math.floor((seconds % (3600 * 24)) / 3600); 258 const minutes = Math.floor((seconds % 3600) / 60); 259 const remainingSeconds = seconds % 60; 260 261 const parts = []; 262 263 if (days > 0) { 264 parts.push(`${days} day${days !== 1 ? "s" : ""}`); 265 } 266 267 if (hours > 0) { 268 parts.push(`${hours} hour${hours !== 1 ? "s" : ""}`); 269 } 270 271 if (minutes > 0) { 272 parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`); 273 } 274 275 if (remainingSeconds > 0) { 276 parts.push( 277 `${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`, 278 ); 279 } 280 281 return parts.join(" and "); 282 }; 283 284 // seconds / minutes /hours / Days / Years calculator 285 export const niceDays = (secondsValue: string, timeVariant: string = "s") => { 286 let seconds = parseFloat(secondsValue); 287 288 return niceDaysInt(seconds, timeVariant); 289 }; 290 291 // niceDaysInt returns the string in the max unit found e.g. 92400 seconds -> 1 day 292 export const niceDaysInt = (seconds: number, timeVariant: string = "s") => { 293 switch (timeVariant) { 294 case "ns": 295 seconds = Math.floor(seconds * 0.000000001); 296 break; 297 case "ms": 298 seconds = Math.floor(seconds * 0.001); 299 break; 300 default: 301 } 302 303 const days = Math.floor(seconds / (3600 * 24)); 304 305 seconds -= days * 3600 * 24; 306 const hours = Math.floor(seconds / 3600); 307 seconds -= hours * 3600; 308 const minutes = Math.floor(seconds / 60); 309 seconds -= minutes * 60; 310 311 if (days > 365) { 312 const years = days / 365; 313 return `${years} year${Math.floor(years) === 1 ? "" : "s"}`; 314 } 315 316 if (days > 30) { 317 const months = Math.floor(days / 30); 318 const diffDays = days - months * 30; 319 320 return `${months} month${Math.floor(months) === 1 ? "" : "s"} ${ 321 diffDays > 0 ? `${diffDays} day${diffDays > 1 ? "s" : ""}` : "" 322 }`; 323 } 324 325 if (days >= 7 && days <= 30) { 326 const weeks = Math.floor(days / 7); 327 328 return `${Math.floor(weeks)} week${weeks === 1 ? "" : "s"}`; 329 } 330 331 if (days >= 1 && days <= 6) { 332 return `${days} day${days > 1 ? "s" : ""}`; 333 } 334 335 return `${hours >= 1 ? `${hours} hour${hours > 1 ? "s" : ""}` : ""} ${ 336 minutes >= 1 && hours === 0 337 ? `${minutes} minute${minutes > 1 ? "s" : ""}` 338 : "" 339 } ${ 340 seconds >= 1 && minutes === 0 && hours === 0 341 ? `${seconds} second${seconds > 1 ? "s" : ""}` 342 : "" 343 }`; 344 }; 345 346 const twoDigitsNumberString = (value: number) => { 347 return `${value < 10 ? "0" : ""}${value}`; 348 }; 349 350 export const getTimeFromTimestamp = ( 351 timestamp: string, 352 fullDate: boolean = false, 353 simplifiedDate: boolean = false, 354 ) => { 355 const timestampToInt = parseInt(timestamp); 356 if (isNaN(timestampToInt)) { 357 return ""; 358 } 359 const dateObject = new Date(timestampToInt * 1000); 360 361 if (fullDate) { 362 if (simplifiedDate) { 363 return `${twoDigitsNumberString( 364 dateObject.getMonth() + 1, 365 )}/${twoDigitsNumberString(dateObject.getDate())} ${twoDigitsNumberString( 366 dateObject.getHours(), 367 )}:${twoDigitsNumberString(dateObject.getMinutes())}`; 368 } else { 369 return dateObject.toLocaleString(); 370 } 371 } 372 return `${dateObject.getHours()}:${String(dateObject.getMinutes()).padStart( 373 2, 374 "0", 375 )}`; 376 }; 377 378 export const calculateBytes = ( 379 x: string | number, 380 showDecimals = false, 381 roundFloor = true, 382 k8sUnit = false, 383 ): IBytesCalc => { 384 let bytes; 385 386 if (typeof x === "string") { 387 bytes = parseInt(x, 10); 388 } else { 389 bytes = x; 390 } 391 392 if (bytes === 0) { 393 return { total: 0, unit: units[0] }; 394 } 395 396 // Gi : GiB 397 const k = 1024; 398 399 // Get unit for measure 400 const i = Math.floor(Math.log(bytes) / Math.log(k)); 401 402 const fractionDigits = showDecimals ? 1 : 0; 403 404 const bytesUnit = bytes / Math.pow(k, i); 405 406 const roundedUnit = roundFloor ? Math.floor(bytesUnit) : bytesUnit; 407 408 // Get Unit parsed 409 const unitParsed = parseFloat(roundedUnit.toFixed(fractionDigits)); 410 const finalUnit = k8sUnit ? k8sCalcUnits[i] : units[i]; 411 412 return { total: unitParsed, unit: finalUnit }; 413 }; 414 415 export const nsToSeconds = (nanoseconds: number) => { 416 const conversion = nanoseconds * 0.000000001; 417 const round = Math.round((conversion + Number.EPSILON) * 10000) / 10000; 418 419 return `${round} s`; 420 }; 421 422 export const textToRGBColor = (text: string) => { 423 const splitText = text.split(""); 424 425 const hashVl = splitText.reduce((acc, currItem) => { 426 return acc + currItem.charCodeAt(0) + ((acc << 5) - acc); 427 }, 0); 428 429 const hashColored = ((hashVl * 100) & 0x00ffffff).toString(16).toUpperCase(); 430 431 return `#${hashColored.padStart(6, "0")}`; 432 }; 433 434 export const prettyNumber = (usage: number | undefined) => { 435 if (usage === undefined) { 436 return 0; 437 } 438 439 return usage.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 440 }; 441 442 export const representationNumber = (number: number | undefined) => { 443 if (number === undefined) { 444 return "0"; 445 } 446 447 let returnValue = number.toString(); 448 let unit = ""; 449 450 if (number > 999 && number < 1000000) { 451 returnValue = (number / 1000).toFixed(1); // convert to K, numbers > 999 452 unit = "K"; 453 } else if (number >= 1000000 && number < 1000000000) { 454 returnValue = (number / 1000000).toFixed(1); // convert to M, numbers >= 1 million 455 unit = "M"; 456 } else if (number >= 1000000000) { 457 returnValue = (number / 1000000000).toFixed(1); // convert to B, numbers >= 1 billion 458 unit = "B"; 459 } 460 461 if (returnValue.endsWith(".0")) { 462 returnValue = returnValue.slice(0, -2); 463 } 464 465 return `${returnValue}${unit}`; 466 }; 467 468 /** Ref https://developer.mozilla.org/en-US/docs/Glossary/Base64 */ 469 470 const base64ToBytes = (base64: any): Uint8Array => { 471 const binString: any = atob(base64); 472 // @ts-ignore 473 return Uint8Array.from(binString, (m) => m.codePointAt(0)); 474 }; 475 476 const bytesToBase64 = (bytes: any) => { 477 const binString = Array.from(bytes, (x: any) => String.fromCodePoint(x)).join( 478 "", 479 ); 480 return btoa(binString); 481 }; 482 483 export const encodeURLString = (name: string | null) => { 484 if (!name) { 485 return ""; 486 } 487 try { 488 return bytesToBase64(new TextEncoder().encode(name)); 489 } catch (err) { 490 return ""; 491 } 492 }; 493 494 export const decodeURLString = (text: string) => { 495 try { 496 return new TextDecoder().decode(base64ToBytes(text)); 497 } catch (err) { 498 return text; 499 } 500 }; 501 502 export const performDownload = (blob: Blob, fileName: string) => { 503 const link = document.createElement("a"); 504 link.href = window.URL.createObjectURL(blob); 505 link.download = fileName; 506 document.body.appendChild(link); 507 link.click(); 508 document.body.removeChild(link); 509 }; 510 511 export const getCookieValue = (cookieName: string) => { 512 return ( 513 document.cookie 514 .match("(^|;)\\s*" + cookieName + "\\s*=\\s*([^;]+)") 515 ?.pop() || "" 516 ); 517 }; 518 519 export const capacityColors = (usedSpace: number, maxSpace: number) => { 520 const percCalculate = (usedSpace * 100) / maxSpace; 521 522 if (percCalculate >= 90) { 523 return "#C83B51"; 524 } else if (percCalculate >= 70) { 525 return "#FFAB0F"; 526 } 527 528 return "#07193E"; 529 }; 530 531 export const getClientOS = (): string => { 532 const getPlatform = get(window.navigator, "platform", "undefined"); 533 534 if (!getPlatform) { 535 return "undefined"; 536 } 537 538 return getPlatform; 539 }; 540 541 // Generates a valid access/secret key string 542 export const getRandomString = function (length = 16): string { 543 let retval = ""; 544 let legalcharacters = 545 "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 546 for (let i = 0; i < length; i++) { 547 retval += 548 legalcharacters[Math.floor(Math.random() * legalcharacters.length)]; 549 } 550 return retval; 551 }; 552 553 // replaces bad unicode characters 554 export const replaceUnicodeChar = (inputString: string): string => { 555 let unicodeChar = "\u202E"; 556 return inputString.split(unicodeChar).join("<�202e>"); 557 }; 558 559 // unescaped characters might throw error like '%' 560 export const safeDecodeURIComponent = (value: any) => { 561 try { 562 return decodeURIComponent(value); 563 } catch (err) { 564 return value; 565 } 566 };