Path: blob/master/src/packages/util/licenses/describe-quota.ts
1447 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6To avoid inconsistency, we are going to follow the style guide/table from7the "Microsoft Writing Style Guide" for things like "3 GB":89https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/bits-bytes-terms1011We were just making stuff up all over in CoCalc based on what other sites12do, and the net result was things got inconsistent.13*/1415import {16LicenseIdleTimeouts,17untangleUptime,18Uptime,19} from "../consts/site-license";20import { capitalize, is_array, plural, round2 } from "../misc";21import { SiteLicenseQuota } from "../types/site-licenses";22import { loadPatch } from "../upgrades/quota";23import { dedicatedDiskDisplay, dedicatedVmDisplay } from "../upgrades/utils";24import type { PurchaseInfo } from "./purchase/types";2526export function describeQuotaFromInfo(27info: Partial<PurchaseInfo>,28withName = true,29): string {30const { type } = info;31switch (type) {32case "quota":33const { idle_timeout, always_running } = untangleUptime(34info.custom_uptime,35);36return describe_quota({37ram: info.custom_ram,38cpu: info.custom_cpu,39disk: info.custom_disk,40always_running,41idle_timeout,42member: info.custom_member,43user: info.user,44boost: info.boost,45run_limit: info.run_limit || info.quantity,46});4748case "vm":49if (info.dedicated_vm == null) {50throw new Error(`dedicated_vm must be defined`);51}52// we make a copy, because we eventually delete a field53const dedicated_vm = { ...info.dedicated_vm };54if (!withName) delete dedicated_vm?.name;55return describe_quota({ dedicated_vm });5657case "disk":58if (59info.dedicated_disk == null ||60typeof info.dedicated_disk === "boolean"61) {62throw new Error(`dedicated_disk must be defined and not a boolean`);63}64// we make a copy, because we eventually delete a field65const dedicated_disk = { ...info.dedicated_disk };66if (!withName) delete dedicated_disk?.name;67return describe_quota({ dedicated_disk });6869default:70throw new Error(`unkonwn type ${type}`);71}72}7374function fixUptime(quota) {75// regarding quota.uptime: it is assumed that all calls already query using the schema defined76// in SiteLicenseQuota, but if not, we untangle the uptime field.77if (quota.uptime != null) {78const { always_running, idle_timeout } = untangleUptime(quota.uptime);79quota.always_running = always_running;80quota.idle_timeout = idle_timeout;81delete quota.uptime;82}83}8485export function describe_quota(86quota: SiteLicenseQuota & { uptime?: Uptime; run_limit?: number },87short?: boolean,88): string {89//console.log(`describe_quota (short=${short})`, quota);9091fixUptime(quota);9293const v: string[] = [];94let intro: string = "";95let hideNetwork = false;96const isBoost = quota.boost === true;97const booster = isBoost ? " booster" : "";98if (quota.user) {99if (short) {100intro = `${capitalize(quota.user)}${booster},`;101} else {102intro = `${capitalize(quota.user)} license${booster} providing`;103}104} else {105// dedicated resources do not have a specific user106intro = short ? "License" : "License providing";107}108109// If onPremQuota becomes true, we only want to show non-trivial upgrades in the license description. Why?110// Usually, upgrades to quotas are provided by other licenses, not the ones that provide on-prem modifications.111let onPremQuota = false;112if (quota.ext_rw) {113onPremQuota = true;114v.push("read/write access to global files");115}116if (typeof quota.patch === "string" && quota.patch.length > 0) {117onPremQuota = true;118const n = loadPatch(quota.patch).length;119v.push(`${n} deployment ${plural(n, "patch", "patches")}`);120}121hideNetwork ||= onPremQuota;122123if (onPremQuota ? (quota.ram ?? 1) > 2 : quota.ram) {124v.push(`${quota.ram} GB RAM`);125}126if (onPremQuota ? (quota.cpu ?? 1) > 1 : quota.cpu) {127v.push(`${quota.cpu} shared ${plural(quota.cpu, "vCPU")}`);128}129if (quota.disk) {130v.push(`${quota.disk} GB disk`);131}132if (quota.dedicated_ram) {133v.push(`${quota.dedicated_ram} GB dedicated RAM`);134}135if (quota.dedicated_cpu) {136v.push(137`${quota.dedicated_cpu} dedicated ${plural(quota.dedicated_cpu, "vCPU")}`,138);139}140if (quota.gpu) {141const { gpu } = quota;142const num = gpu === true ? 1 : (gpu.num ?? 1);143v.push(`${num} GPU(s)`);144}145146if (147typeof quota.dedicated_vm !== "boolean" &&148typeof quota.dedicated_vm?.machine === "string"149) {150hideNetwork = true;151v.push(152`hosting on a Dedicated VM providing ${dedicatedVmDisplay(153quota.dedicated_vm,154)}`,155);156} else {157if (quota.member) {158v.push("member" + (short ? "" : " hosting"));159}160}161162if (163quota.dedicated_disk != null &&164typeof quota.dedicated_disk !== "boolean"165) {166hideNetwork = true;167v.push(`a Dedicated Disk (${dedicatedDiskDisplay(quota.dedicated_disk)})`);168}169170if (quota.always_running) {171v.push("always running");172} else {173if (quota.idle_timeout != null) {174const it = LicenseIdleTimeouts[quota.idle_timeout];175if (it != null && (onPremQuota ? quota.idle_timeout != "short" : true)) {176v.push(`${it.label} timeout`);177}178}179}180if (!hideNetwork && !isBoost) {181v.push("network"); // always provided, because we trust customers.182}183if (quota.run_limit) {184v.push(185`up to ${quota.run_limit} running ${plural(quota.run_limit, "project")}`,186);187}188let describePeriod = "";189const period = quota["period"];190const range = quota["range"];191if (period) {192if (period == "monthly") {193describePeriod = " (monthly subscription)";194} else if (period == "yearly") {195describePeriod = " (yearly subscription)";196} else if (197period == "range" &&198range != null &&199is_array(range) &&200range.length == 2201) {202// specific range203const v = range.map((x) => new Date(x).toLocaleString());204const days = round2(205(new Date(range[1]).valueOf() - new Date(range[0]).valueOf()) /206(1000 * 60 * 60 * 24),207);208describePeriod = ` (${v[0]} - ${v[1]}, ${days} ${plural(Math.round(days), "day")})`;209}210}211212return `${intro} ${v.join(", ")}${describePeriod}`;213}214215// similar to the above, but very short for the info bar on those store purchase pages.216// instead of overloading the above with even more special cases, this brings it quickly to the point217export function describeQuotaOnLine(218quota: SiteLicenseQuota & { uptime?: Uptime; run_limit?: number },219): string {220fixUptime(quota);221222const v: string[] = [];223224if (quota.ram) {225v.push(`${quota.ram} GB RAM`);226}227if (quota.cpu) {228v.push(`${quota.cpu} ${plural(quota.cpu, "vCPU")}`);229}230if (quota.disk) {231v.push(`${quota.disk} GB disk`);232}233if (quota.dedicated_ram) {234v.push(`${quota.dedicated_ram} GB dedicated RAM`);235}236if (quota.dedicated_cpu) {237v.push(238`${quota.dedicated_cpu} dedicated ${plural(quota.dedicated_cpu, "vCPU")}`,239);240}241242if (243typeof quota.dedicated_vm !== "boolean" &&244typeof quota.dedicated_vm?.machine === "string"245) {246v.push(`Dedicated VM ${dedicatedVmDisplay(quota.dedicated_vm)}`);247} else {248if (quota.member) {249v.push("member");250}251}252253if (254quota.dedicated_disk != null &&255typeof quota.dedicated_disk !== "boolean"256) {257v.push(258`Dedicated Disk (${dedicatedDiskDisplay(quota.dedicated_disk, "short")})`,259);260}261262if (quota.always_running) {263v.push("always running");264} else {265if (quota.idle_timeout != null) {266const it = LicenseIdleTimeouts[quota.idle_timeout];267if (it != null) {268v.push(`${it.labelShort} timeout`);269}270}271}272273if (quota.user) {274const isBoost = quota.boost === true;275const booster = isBoost ? " booster" : "";276v.push(`${capitalize(quota.user)}${booster}`);277}278279if (quota.run_limit) {280v.push(`up to ${quota.run_limit} ${plural(quota.run_limit, "project")}`);281}282283return `${v.join(", ")}`;284}285286287