Skip to content

Commit bc51e0c

Browse files
committed
Merge remote-tracking branch 'origin/master' into llm-tweak-abuse
2 parents f831cc3 + 92011f5 commit bc51e0c

File tree

166 files changed

+10162
-906
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

166 files changed

+10162
-906
lines changed

.github/workflows/make-and-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
- name: Install python3 requests
4141
run: sudo apt-get install python3-requests
4242
- name: Check doc links
43-
run: cd src/scripts && python3 check_doc_urls.py
43+
run: cd src/scripts && python3 check_doc_urls.py || sleep 5 || python3 check_doc_urls.py
4444

4545
build:
4646
runs-on: ubuntu-latest

src/packages/database/postgres/schema/table.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export async function createTable(
3939
if (info.unique) {
4040
s += " UNIQUE";
4141
}
42+
if (info.not_null) {
43+
s += " NOT NULL";
44+
}
4245
if (info.pg_check) {
4346
s += " " + info.pg_check;
4447
}

src/packages/database/settings/customize.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface Customize {
5858
neuralSearchEnabled?: boolean;
5959
jupyterApiEnabled?: boolean;
6060
computeServersEnabled?: boolean;
61+
cloudFilesystemsEnabled?: boolean;
6162
githubProjectId?: string;
6263
support?: string;
6364
}
@@ -167,6 +168,7 @@ export default async function getCustomize(): Promise<Customize> {
167168
jupyterApiEnabled: settings.jupyter_api_enabled,
168169

169170
computeServersEnabled: settings.compute_servers_enabled,
171+
cloudFilesystemsEnabled: settings.cloud_filesystems_enabled,
170172

171173
// GitHub proxy project
172174
githubProjectId: settings.github_project_id,

src/packages/frontend/account/account-page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { UpgradesPage } from "./upgrades/upgrades-page";
2929
import PurchasesPage from "@cocalc/frontend/purchases/purchases-page";
3030
import SubscriptionsPage from "@cocalc/frontend/purchases/subscriptions-page";
3131
import StatementsPage from "@cocalc/frontend/purchases/statements-page";
32+
import { cloudFilesystemsEnabled } from "@cocalc/frontend/compute";
33+
import CloudFilesystems from "@cocalc/frontend/compute/cloud-filesystem/cloud-filesystems";
3234

3335
export const AccountPage: React.FC = () => {
3436
const active_page = useTypedRedux("account", "active_page");
@@ -218,6 +220,17 @@ export const AccountPage: React.FC = () => {
218220
children: active_page === "upgrades" && <UpgradesPage />,
219221
});
220222
}
223+
if (cloudFilesystemsEnabled()) {
224+
items.push({
225+
key: "cloud-filesystems",
226+
label: (
227+
<>
228+
<Icon name="disk-round" /> Cloud Filesystems
229+
</>
230+
),
231+
children: <CloudFilesystems />,
232+
});
233+
}
221234

222235
return items;
223236
}

src/packages/frontend/client/api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ async function callApi(endpoint: string, args?: object) {
3232
},
3333
...(args != null ? { body: JSON.stringify(args) } : undefined),
3434
});
35-
// const respClone = resp.clone();
35+
const respClone = resp.clone();
3636
let json: any = null;
3737
try {
3838
json = await resp.json();
3939
} catch (e) {
40-
// console.log(await respClone.text());
40+
console.log(e);
41+
console.log(await respClone.text());
4142
throw Error("API server is down -- try again later");
4243
}
4344
if (json == null) {

src/packages/frontend/client/project.ts

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as message from "@cocalc/util/message";
2020
import { DirectoryListingEntry } from "@cocalc/util/types";
2121
import { connection_to_project } from "../project/websocket/connect";
2222
import { API } from "../project/websocket/api";
23+
import httpApi from "./api";
2324
import { redux } from "../app-framework";
2425
import { WebappClient } from "./client";
2526
import { allow_project_to_run } from "../project/client-side-throttle";
@@ -36,31 +37,7 @@ import { appBasePath } from "@cocalc/frontend/customize/app-base-path";
3637
import { ipywidgetsGetBufferUrl } from "@cocalc/frontend/jupyter/server-urls";
3738
import type { ApiKey } from "@cocalc/util/db-schema/api-keys";
3839
import computeServers from "@cocalc/frontend/compute/manager";
39-
40-
export interface ExecOpts {
41-
project_id: string;
42-
compute_server_id?: number; // if true, run on the compute server (if available)
43-
filesystem?: boolean; // run in fileserver container on compute server; otherwise, runs on main compute container.
44-
path?: string;
45-
command: string;
46-
args?: string[];
47-
timeout?: number;
48-
max_output?: number;
49-
bash?: boolean;
50-
aggregate?: string | number | { value: string | number };
51-
err_on_exit?: boolean;
52-
env?: { [key: string]: string }; // custom environment variables.
53-
cb?: Function; // if given use a callback interface *instead* of async.
54-
}
55-
export const ExecOpts = null; // webpack + TS es2020 modules need this
56-
57-
export interface ExecOutput {
58-
stdout: string;
59-
stderr: string;
60-
exit_code: number;
61-
time: number; // time in ms, from user point of view.
62-
}
63-
export const ExecOutput = null; // webpack + TS es2020 modules need this
40+
import type { ExecOpts, ExecOutput } from "@cocalc/util/db-schema/projects";
6441

6542
export class ProjectClient {
6643
private client: WebappClient;
@@ -208,7 +185,7 @@ export class ProjectClient {
208185
time" (which is stored in the db), which they client will know. This is used, e.g.,
209186
for operations like "run rst2html on this file whenever it is saved."
210187
*/
211-
public async exec(opts: ExecOpts): Promise<ExecOutput> {
188+
public async exec(opts: ExecOpts & { post?: boolean }): Promise<ExecOutput> {
212189
opts = defaults(opts, {
213190
project_id: required,
214191
compute_server_id: undefined,
@@ -222,6 +199,7 @@ export class ProjectClient {
222199
aggregate: undefined,
223200
err_on_exit: true,
224201
env: undefined,
202+
post: false, // if true, uses the POST api through nextjs instead of the websocket api.
225203
cb: undefined, // if given use a callback interface instead of async
226204
});
227205

@@ -239,11 +217,19 @@ export class ProjectClient {
239217
};
240218
}
241219

242-
try {
243-
const ws = await this.websocket(opts.project_id);
244-
const exec_opts = copy_without(opts, ["project_id"]);
220+
const { post } = opts;
221+
delete opts.post;
245222

246-
const msg = await ws.api.exec(exec_opts);
223+
try {
224+
let msg;
225+
if (post) {
226+
// use post API
227+
msg = await httpApi("exec", opts);
228+
} else {
229+
const ws = await this.websocket(opts.project_id);
230+
const exec_opts = copy_without(opts, ["project_id"]);
231+
msg = await ws.api.exec(exec_opts);
232+
}
247233
if (msg.status && msg.status == "error") {
248234
throw new Error(msg.error);
249235
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useRef } from "react";
2+
import ReactPlotly from "react-plotly.js";
3+
import useResizeObserver from "use-resize-observer";
4+
5+
export default function Plot(props) {
6+
const divRef = useRef<HTMLDivElement>(null);
7+
const resize = useResizeObserver({ ref: divRef });
8+
return (
9+
<div ref={divRef} style={props.style}>
10+
<ReactPlotly
11+
{...props}
12+
layout={{ ...props.layout, width: resize.width }}
13+
/>
14+
</div>
15+
);
16+
}

src/packages/frontend/compute/api.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import api from "@cocalc/frontend/client/api";
2+
("");
23
import type {
34
Action,
45
ComputeServerTemplate,
@@ -123,6 +124,23 @@ export const getGoogleCloudPriceData = reuseInFlight(
123124
},
124125
);
125126

127+
import { useState, useEffect } from "react";
128+
export function useGoogleCloudPriceData() {
129+
const [priceData, setPriceData] = useState<null | GoogleCloudData>(null);
130+
const [error, setError] = useState<string>("");
131+
useEffect(() => {
132+
(async () => {
133+
try {
134+
setError("");
135+
setPriceData(await getGoogleCloudPriceData());
136+
} catch (err) {
137+
setError(`${err}`);
138+
}
139+
})();
140+
}, []);
141+
return [priceData, error];
142+
}
143+
126144
// Cache for 5 minutes -- cache less since this includes realtime
127145
// information about GPU availability.
128146
let hyperstackPriceData: HyperstackPriceData | null = null;
@@ -168,9 +186,17 @@ export async function getLog(opts: { id }) {
168186
return await api("compute/get-log", opts);
169187
}
170188

171-
export const getTitle = reuseInFlight(async (opts: { id: number }) => {
172-
return await api("compute/get-server-title", opts);
173-
});
189+
export const getTitle = reuseInFlight(
190+
async (opts: {
191+
id: number;
192+
}): Promise<{
193+
title: string;
194+
color: string;
195+
project_specific_id: number;
196+
}> => {
197+
return await api("compute/get-server-title", opts);
198+
},
199+
);
174200

175201
// Setting a detailed state component for a compute server
176202
export async function setDetailedState(opts: {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { CHECK_IN_PATH } from "@cocalc/util/db-schema/compute-servers";
2+
import { webapp_client } from "@cocalc/frontend/webapp-client";
3+
import { redux } from "@cocalc/frontend/app-framework";
4+
5+
// cause the given compute server to check in, or all running compute
6+
// servers in the project if compute_server_id isn't specified.
7+
export async function checkIn({
8+
project_id,
9+
compute_server_id,
10+
}: {
11+
project_id: string;
12+
compute_server_id: number;
13+
}) {
14+
if (compute_server_id != null) {
15+
await webapp_client.project_client.exec({
16+
command: "touch",
17+
args: [CHECK_IN_PATH],
18+
project_id,
19+
compute_server_id,
20+
});
21+
return;
22+
}
23+
}
24+
25+
// launches in parallel check in on all running projects; doesn't
26+
// wait for response.
27+
export function checkInAll(project_id) {
28+
const computeServers = redux
29+
.getProjectStore(project_id)
30+
.get("compute_servers");
31+
if (computeServers == null) {
32+
return;
33+
}
34+
const ids = computeServers
35+
.filter((x) => x.get("state") == "running")
36+
.map((x) => x.get("id"))
37+
.keySeq();
38+
for (const id of ids) {
39+
(async () => {
40+
try {
41+
await checkIn({ project_id, compute_server_id: id });
42+
} catch (err) {
43+
console.warn(`checkIn issue with compute server ${id}`, err);
44+
}
45+
})();
46+
}
47+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import api from "@cocalc/frontend/client/api";
2+
import LRU from "lru-cache";
3+
import type {
4+
CreateCloudFilesystem,
5+
CloudFilesystem,
6+
EditCloudFilesystem,
7+
} from "@cocalc/util/db-schema/cloud-filesystems";
8+
import {
9+
CHANGE_MOUNTED,
10+
CHANGE_UNMOUNTED,
11+
} from "@cocalc/util/db-schema/cloud-filesystems";
12+
13+
export async function createCloudFilesystem(
14+
opts: CreateCloudFilesystem,
15+
): Promise<number> {
16+
return await api("compute/cloud-filesystem/create", opts);
17+
}
18+
19+
export async function deleteCloudFilesystem({
20+
id,
21+
lock,
22+
}: {
23+
id: number;
24+
lock: string;
25+
}): Promise<void> {
26+
await api("compute/cloud-filesystem/delete", { id, lock });
27+
}
28+
29+
export async function editCloudFilesystem(
30+
opts: EditCloudFilesystem,
31+
): Promise<void> {
32+
for (const field in opts) {
33+
if (field == "id") {
34+
continue;
35+
}
36+
if (!CHANGE_MOUNTED.has(field) && !CHANGE_UNMOUNTED.has(field)) {
37+
throw Error(`invalid field '${field}' for edit`);
38+
}
39+
}
40+
return await api("compute/cloud-filesystem/edit", opts);
41+
}
42+
43+
const cloudFilesystemCache = new LRU<string, CloudFilesystem[]>({
44+
max: 100,
45+
ttl: 1000 * 30,
46+
});
47+
48+
export async function getCloudFilesystems(
49+
// give no options to get all filesystems that YOU own across all your projects
50+
opts: {
51+
// id = specific on
52+
id?: number;
53+
// project_id = all in a given project (owned by anybody)
54+
project_id?: string;
55+
// if true, use cache and potentially return stale data (good for repeated calls in a list, etc.)
56+
cache?: boolean;
57+
} = {},
58+
): Promise<CloudFilesystem[]> {
59+
const key = `${opts.id}${opts.project_id}`;
60+
if (opts.cache && cloudFilesystemCache.has(key)) {
61+
return cloudFilesystemCache.get(key)!;
62+
}
63+
const filesystems = await api("compute/cloud-filesystem/get", {
64+
id: opts.id,
65+
project_id: opts.project_id,
66+
});
67+
// always save in cache
68+
cloudFilesystemCache.set(key, filesystems);
69+
return filesystems;
70+
}
71+
72+
export async function getMetrics({
73+
id,
74+
limit,
75+
offset,
76+
}: {
77+
id: number;
78+
limit?: number;
79+
offset?: number;
80+
}) {
81+
return await api("compute/cloud-filesystem/get-metrics", {
82+
cloud_filesystem_id: id,
83+
limit,
84+
offset,
85+
});
86+
}

0 commit comments

Comments
 (0)