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