CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/licenses/describe-quota.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
To avoid inconsistency, we are going to follow the style guide/table from
8
the "Microsoft Writing Style Guide" for things like "3 GB":
9
10
https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/bits-bytes-terms
11
12
We were just making stuff up all over in CoCalc based on what other sites
13
do, and the net result was things got inconsistent.
14
*/
15
16
import {
17
LicenseIdleTimeouts,
18
untangleUptime,
19
Uptime,
20
} from "../consts/site-license";
21
import { capitalize, plural } from "../misc";
22
import { SiteLicenseQuota } from "../types/site-licenses";
23
import { loadPatch } from "../upgrades/quota";
24
import { dedicatedDiskDisplay, dedicatedVmDisplay } from "../upgrades/utils";
25
import type { PurchaseInfo } from "./purchase/types";
26
27
export function describeQuotaFromInfo(
28
info: Partial<PurchaseInfo>,
29
withName = true,
30
): string {
31
const { type } = info;
32
switch (type) {
33
case "quota":
34
const { idle_timeout, always_running } = untangleUptime(
35
info.custom_uptime,
36
);
37
return describe_quota({
38
ram: info.custom_ram,
39
cpu: info.custom_cpu,
40
disk: info.custom_disk,
41
always_running,
42
idle_timeout,
43
member: info.custom_member,
44
user: info.user,
45
boost: info.boost,
46
run_limit: info.run_limit || info.quantity,
47
});
48
49
case "vm":
50
if (info.dedicated_vm == null) {
51
throw new Error(`dedicated_vm must be defined`);
52
}
53
// we make a copy, because we eventually delete a field
54
const dedicated_vm = { ...info.dedicated_vm };
55
if (!withName) delete dedicated_vm?.name;
56
return describe_quota({ dedicated_vm });
57
58
case "disk":
59
if (
60
info.dedicated_disk == null ||
61
typeof info.dedicated_disk === "boolean"
62
) {
63
throw new Error(`dedicated_disk must be defined and not a boolean`);
64
}
65
// we make a copy, because we eventually delete a field
66
const dedicated_disk = { ...info.dedicated_disk };
67
if (!withName) delete dedicated_disk?.name;
68
return describe_quota({ dedicated_disk });
69
70
default:
71
throw new Error(`unkonwn type ${type}`);
72
}
73
}
74
75
function fixUptime(quota) {
76
// regarding quota.uptime: it is assumed that all calls already query using the schema defined
77
// in SiteLicenseQuota, but if not, we untangle the uptime field.
78
if (quota.uptime != null) {
79
const { always_running, idle_timeout } = untangleUptime(quota.uptime);
80
quota.always_running = always_running;
81
quota.idle_timeout = idle_timeout;
82
delete quota.uptime;
83
}
84
}
85
86
export function describe_quota(
87
quota: SiteLicenseQuota & { uptime?: Uptime; run_limit?: number },
88
short?: boolean,
89
): string {
90
//console.log(`describe_quota (short=${short})`, quota);
91
92
fixUptime(quota);
93
94
const v: string[] = [];
95
let intro: string = "";
96
let hideNetwork = false;
97
const isBoost = quota.boost === true;
98
const booster = isBoost ? " booster" : "";
99
if (quota.user) {
100
if (short) {
101
intro = `${capitalize(quota.user)}${booster},`;
102
} else {
103
intro = `${capitalize(quota.user)} license${booster} providing`;
104
}
105
} else {
106
// dedicated resources do not have a specific user
107
intro = short ? "License" : "License providing";
108
}
109
110
// If onPremQuota becomes true, we only want to show non-trivial upgrades in the license description. Why?
111
// Usually, upgrades to quotas are provided by other licenses, not the ones that provide on-prem modifications.
112
let onPremQuota = false;
113
if (quota.ext_rw) {
114
onPremQuota = true;
115
v.push("read/write access to global files");
116
}
117
if (typeof quota.patch === "string" && quota.patch.length > 0) {
118
onPremQuota = true;
119
const n = loadPatch(quota.patch).length;
120
v.push(`${n} deployment ${plural(n, "patch", "patches")}`);
121
}
122
hideNetwork ||= onPremQuota;
123
124
if (onPremQuota ? (quota.ram ?? 1) > 2 : quota.ram) {
125
v.push(`${quota.ram} GB RAM`);
126
}
127
if (onPremQuota ? (quota.cpu ?? 1) > 1 : quota.cpu) {
128
v.push(`${quota.cpu} shared ${plural(quota.cpu, "vCPU")}`);
129
}
130
if (quota.disk) {
131
v.push(`${quota.disk} GB disk`);
132
}
133
if (quota.dedicated_ram) {
134
v.push(`${quota.dedicated_ram} GB dedicated RAM`);
135
}
136
if (quota.dedicated_cpu) {
137
v.push(
138
`${quota.dedicated_cpu} dedicated ${plural(quota.dedicated_cpu, "vCPU")}`,
139
);
140
}
141
if (quota.gpu) {
142
const { gpu } = quota;
143
const num = gpu === true ? 1 : gpu.num ?? 1;
144
v.push(`${num} GPU(s)`);
145
}
146
147
if (
148
typeof quota.dedicated_vm !== "boolean" &&
149
typeof quota.dedicated_vm?.machine === "string"
150
) {
151
hideNetwork = true;
152
v.push(
153
`hosting on a Dedicated VM providing ${dedicatedVmDisplay(
154
quota.dedicated_vm,
155
)}`,
156
);
157
} else {
158
if (quota.member) {
159
v.push("member" + (short ? "" : " hosting"));
160
}
161
}
162
163
if (
164
quota.dedicated_disk != null &&
165
typeof quota.dedicated_disk !== "boolean"
166
) {
167
hideNetwork = true;
168
v.push(`a Dedicated Disk (${dedicatedDiskDisplay(quota.dedicated_disk)})`);
169
}
170
171
if (quota.always_running) {
172
v.push("always running");
173
} else {
174
if (quota.idle_timeout != null) {
175
const it = LicenseIdleTimeouts[quota.idle_timeout];
176
if (it != null && (onPremQuota ? quota.idle_timeout != "short" : true)) {
177
v.push(`${it.label} timeout`);
178
}
179
}
180
}
181
if (!hideNetwork && !isBoost) {
182
v.push("network"); // always provided, because we trust customers.
183
}
184
if (quota.run_limit) {
185
v.push(
186
`up to ${quota.run_limit} simultaneous running ${plural(
187
quota.run_limit,
188
"project",
189
)}`,
190
);
191
}
192
return `${intro} ${v.join(", ")}`;
193
}
194
195
// similar to the above, but very short for the info bar on those store purchase pages.
196
// instead of overloading the above with even more special cases, this brings it quickly to the point
197
export function describeQuotaOnLine(
198
quota: SiteLicenseQuota & { uptime?: Uptime; run_limit?: number },
199
): string {
200
fixUptime(quota);
201
202
const v: string[] = [];
203
204
if (quota.ram) {
205
v.push(`${quota.ram} GB RAM`);
206
}
207
if (quota.cpu) {
208
v.push(`${quota.cpu} ${plural(quota.cpu, "vCPU")}`);
209
}
210
if (quota.disk) {
211
v.push(`${quota.disk} GB disk`);
212
}
213
if (quota.dedicated_ram) {
214
v.push(`${quota.dedicated_ram} GB dedicated RAM`);
215
}
216
if (quota.dedicated_cpu) {
217
v.push(
218
`${quota.dedicated_cpu} dedicated ${plural(quota.dedicated_cpu, "vCPU")}`,
219
);
220
}
221
222
if (
223
typeof quota.dedicated_vm !== "boolean" &&
224
typeof quota.dedicated_vm?.machine === "string"
225
) {
226
v.push(`Dedicated VM ${dedicatedVmDisplay(quota.dedicated_vm)}`);
227
} else {
228
if (quota.member) {
229
v.push("member");
230
}
231
}
232
233
if (
234
quota.dedicated_disk != null &&
235
typeof quota.dedicated_disk !== "boolean"
236
) {
237
v.push(
238
`Dedicated Disk (${dedicatedDiskDisplay(quota.dedicated_disk, "short")})`,
239
);
240
}
241
242
if (quota.always_running) {
243
v.push("always running");
244
} else {
245
if (quota.idle_timeout != null) {
246
const it = LicenseIdleTimeouts[quota.idle_timeout];
247
if (it != null) {
248
v.push(`${it.labelShort} timeout`);
249
}
250
}
251
}
252
253
if (quota.user) {
254
const isBoost = quota.boost === true;
255
const booster = isBoost ? " booster" : "";
256
v.push(`${capitalize(quota.user)}${booster}`);
257
}
258
259
if (quota.run_limit) {
260
v.push(`up to ${quota.run_limit} ${plural(quota.run_limit, "project")}`);
261
}
262
263
return `${v.join(", ")}`;
264
}
265
266