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