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/frontend/compute/api.ts
Views: 687
1
import api from "@cocalc/frontend/client/api";
2
("");
3
import type {
4
Action,
5
ComputeServerTemplate,
6
Configuration,
7
Cloud,
8
Images,
9
GoogleCloudImages,
10
} from "@cocalc/util/db-schema/compute-servers";
11
import type { GoogleCloudData } from "@cocalc/util/compute/cloud/google-cloud/compute-cost";
12
import type { HyperstackPriceData } from "@cocalc/util/compute/cloud/hyperstack/pricing";
13
import type {
14
ConfigurationTemplate,
15
ConfigurationTemplates,
16
} from "@cocalc/util/compute/templates";
17
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
18
import TTL from "@isaacs/ttlcache";
19
20
export async function createServer(opts: {
21
project_id: string;
22
title?: string;
23
color?: string;
24
idle_timeout?: number;
25
autorestart?: boolean;
26
cloud?: Cloud;
27
configuration?: Configuration;
28
notes?: string;
29
}): Promise<number> {
30
return await api("compute/create-server", opts);
31
}
32
33
export async function computeServerAction(opts: {
34
id: number;
35
action: Action;
36
}) {
37
await api("compute/compute-server-action", opts);
38
}
39
40
export async function getServers(opts: { id?: number; project_id: string }) {
41
return await api("compute/get-servers", opts);
42
}
43
44
export async function getServerState(id: number) {
45
return await api("compute/get-server-state", { id });
46
}
47
48
export async function getSerialPortOutput(id: number) {
49
return await api("compute/get-serial-port-output", { id });
50
}
51
52
export async function deleteServer(id: number) {
53
await api("compute/delete-server", { id });
54
}
55
56
export async function undeleteServer(id: number) {
57
await api("compute/undelete-server", { id });
58
}
59
60
// only owner can change properties of a compute server.
61
62
export async function setServerColor(opts: { id: number; color: string }) {
63
return await api("compute/set-server-color", opts);
64
}
65
66
export async function setServerTitle(opts: { id: number; title: string }) {
67
return await api("compute/set-server-title", opts);
68
}
69
70
export async function setServerConfiguration(opts: {
71
id: number;
72
configuration;
73
}) {
74
return await api("compute/set-server-configuration", opts);
75
}
76
77
// only for admins!
78
export async function setTemplate(opts: {
79
id: number;
80
template: ComputeServerTemplate;
81
}) {
82
return await api("compute/set-template", opts);
83
}
84
85
// 5-minute client side ttl cache of all and specific template, since
86
// templates change rarely.
87
88
const templatesCache = new TTL({ ttl: 60 * 1000 * 5 });
89
90
export async function getTemplate(id: number): Promise<ConfigurationTemplate> {
91
if (templatesCache.has(id)) {
92
return templatesCache.get(id)!;
93
}
94
const x = await api("compute/get-template", { id });
95
templatesCache.set(id, x);
96
return x;
97
}
98
99
export async function getTemplates(): Promise<ConfigurationTemplates> {
100
if (templatesCache.has("templates")) {
101
return templatesCache.get("templates")!;
102
}
103
const x = await api("compute/get-templates");
104
templatesCache.set("templates", x);
105
return x;
106
}
107
108
export async function setServerCloud(opts: { id: number; cloud: string }) {
109
return await api("compute/set-server-cloud", opts);
110
}
111
112
// Cache for 12 hours
113
let googleCloudPriceData: GoogleCloudData | null = null;
114
let googleCloudPriceDataExpire: number = 0;
115
export const getGoogleCloudPriceData = reuseInFlight(
116
async (): Promise<GoogleCloudData> => {
117
if (
118
googleCloudPriceData == null ||
119
Date.now() >= googleCloudPriceDataExpire
120
) {
121
googleCloudPriceData = await api("compute/google-cloud/get-pricing-data");
122
googleCloudPriceDataExpire = Date.now() + 1000 * 60 * 60 * 12; // 12 hour cache
123
}
124
if (googleCloudPriceData == null) {
125
throw Error("bug");
126
}
127
return googleCloudPriceData;
128
},
129
);
130
131
import { useState, useEffect } from "react";
132
export function useGoogleCloudPriceData() {
133
const [priceData, setPriceData] = useState<null | GoogleCloudData>(null);
134
const [error, setError] = useState<string>("");
135
useEffect(() => {
136
(async () => {
137
try {
138
setError("");
139
setPriceData(await getGoogleCloudPriceData());
140
} catch (err) {
141
setError(`${err}`);
142
}
143
})();
144
}, []);
145
return [priceData, error];
146
}
147
148
// Cache for 5 minutes -- cache less since this includes realtime
149
// information about GPU availability.
150
let hyperstackPriceData: HyperstackPriceData | null = null;
151
let hyperstackPriceDataExpire: number = 0;
152
export const getHyperstackPriceData = reuseInFlight(
153
async (): Promise<HyperstackPriceData> => {
154
if (
155
hyperstackPriceData == null ||
156
Date.now() >= hyperstackPriceDataExpire
157
) {
158
hyperstackPriceData = await api("compute/get-hyperstack-pricing-data");
159
hyperstackPriceDataExpire = Date.now() + 1000 * 60 * 5; // 5 minute cache
160
}
161
if (hyperstackPriceData == null) {
162
throw Error("bug");
163
}
164
return hyperstackPriceData;
165
},
166
);
167
168
// Returns network usage during the given interval. Returns
169
// amount in GiB and cost at our current rate.
170
export async function getNetworkUsage(opts: {
171
id: number;
172
start: Date;
173
end: Date;
174
}): Promise<{ amount: number; cost: number }> {
175
return await api("compute/get-network-usage", opts);
176
}
177
178
// Get the current api key for a specific (on prem) server.
179
// We only need this for on prem, so we are restricting to that right now.
180
// If no key is allocated, one will be created.
181
export async function getApiKey(opts: { id }): Promise<string> {
182
return await api("compute/get-api-key", opts);
183
}
184
export async function deleteApiKey(opts: { id }): Promise<string> {
185
return await api("compute/delete-api-key", opts);
186
}
187
188
// Get the project log entries directly for just one compute server
189
export async function getLog(opts: { id }) {
190
return await api("compute/get-log", opts);
191
}
192
193
export const getTitle = reuseInFlight(
194
async (opts: {
195
id: number;
196
}): Promise<{
197
title: string;
198
color: string;
199
project_specific_id: number;
200
}> => {
201
return await api("compute/get-server-title", opts);
202
},
203
);
204
205
// Setting a detailed state component for a compute server
206
export async function setDetailedState(opts: {
207
project_id: string;
208
id: number;
209
name: string;
210
state?: string;
211
extra?: string;
212
timeout?: number;
213
progress?: number;
214
}) {
215
return await api("compute/set-detailed-state", opts);
216
}
217
218
// We cache images for 5 minutes.
219
const IMAGES_TTL = 5 * 60 * 1000;
220
221
const imagesCache: {
222
[cloud: string]: { timestamp: number; images: Images | null };
223
} = {};
224
225
function cacheHas(cloud: string) {
226
const x = imagesCache[cloud];
227
if (x == null) {
228
return false;
229
}
230
if (Math.abs(x.timestamp - Date.now()) <= IMAGES_TTL) {
231
return true;
232
}
233
return false;
234
}
235
236
function cacheGet(cloud) {
237
return imagesCache[cloud]?.images;
238
}
239
240
function cacheSet(cloud, images) {
241
imagesCache[cloud] = { images, timestamp: Date.now() };
242
}
243
244
async function getImagesFor({
245
cloud,
246
endpoint,
247
reload,
248
}: {
249
cloud: string;
250
endpoint: string;
251
reload?: boolean;
252
}): Promise<any> {
253
if (!reload && cacheHas(cloud)) {
254
return cacheGet(cloud);
255
}
256
257
try {
258
const images = await api(
259
endpoint,
260
// admin reload forces fetch data from github and/or google cloud - normal users just have their cache ignored above
261
reload ? { noCache: true } : undefined,
262
);
263
cacheSet(cloud, images);
264
return images;
265
} catch (err) {
266
const images = cacheGet(cloud);
267
if (images != null) {
268
console.warn(
269
"ERROR getting updated compute server images -- using cached data",
270
err,
271
);
272
return images;
273
}
274
throw err;
275
}
276
}
277
278
export async function getImages(reload?: boolean): Promise<Images> {
279
return await getImagesFor({
280
cloud: "",
281
endpoint: "compute/get-images",
282
reload,
283
});
284
}
285
286
export async function getGoogleCloudImages(
287
reload?: boolean,
288
): Promise<GoogleCloudImages> {
289
return await getImagesFor({
290
cloud: "google",
291
endpoint: "compute/get-images-google",
292
reload,
293
});
294
}
295
296
export async function setImageTested(opts: {
297
id: number; // server id
298
tested: boolean;
299
}) {
300
return await api("compute/set-image-tested", opts);
301
}
302
303