Skip to content

Commit 3e5c29f

Browse files
committed
Merge branch 'master' into issue-7850
2 parents 0f0ab5c + 3605c72 commit 3e5c29f

34 files changed

+1301
-162
lines changed

src/packages/frontend/app/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,10 @@ export const Page: React.FC = () => {
311311
}}
312312
name={"projects"}
313313
active_top_tab={active_top_tab}
314-
tooltip="Show all the projects on which you collaborate."
314+
tooltip={intl.formatMessage({
315+
id: "page.project_nav.tooltip",
316+
defaultMessage: "Show all the projects on which you collaborate.",
317+
})}
315318
icon="edit"
316319
label={intl.formatMessage(labels.projects)}
317320
/>

src/packages/frontend/chat/chat-indicator.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
// this is... so for now it still sort of toggles. For now things
1010
// do work properly via a hack in close_chat in project_actions.
1111

12+
import { filename_extension } from "@cocalc/util/misc";
1213
import { Button, Tooltip } from "antd";
1314
import { debounce } from "lodash";
14-
import { filename_extension } from "@cocalc/util/misc";
1515
import { useMemo } from "react";
16-
import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";
17-
import { Icon } from "@cocalc/frontend/components/icon";
16+
import { FormattedMessage, useIntl } from "react-intl";
17+
1818
import { UsersViewing } from "@cocalc/frontend/account/avatar/users-viewing";
19+
import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";
1920
import { HiddenXS } from "@cocalc/frontend/components";
21+
import { Icon } from "@cocalc/frontend/components/icon";
2022
import track from "@cocalc/frontend/user-tracking";
23+
import { labels } from "../i18n";
2124

2225
export type ChatState =
2326
| "" // not opened (also undefined counts as not open)
@@ -29,12 +32,12 @@ const CHAT_INDICATOR_STYLE: React.CSSProperties = {
2932
fontSize: "15pt",
3033
paddingTop: "3px",
3134
cursor: "pointer",
32-
};
35+
} as const;
3336

3437
const USERS_VIEWING_STYLE: React.CSSProperties = {
3538
maxWidth: "120px",
3639
marginRight: "5px",
37-
};
40+
} as const;
3841

3942
interface Props {
4043
project_id: string;
@@ -47,6 +50,7 @@ export function ChatIndicator({ project_id, path, chatState }: Props) {
4750
...CHAT_INDICATOR_STYLE,
4851
...{ display: "flex" },
4952
};
53+
5054
return (
5155
<div style={style}>
5256
<UsersViewing
@@ -60,6 +64,8 @@ export function ChatIndicator({ project_id, path, chatState }: Props) {
6064
}
6165

6266
function ChatButton({ project_id, path, chatState }) {
67+
const intl = useIntl();
68+
6369
const toggleChat = debounce(
6470
() => {
6571
const actions = redux.getProjectActions(project_id);
@@ -92,7 +98,10 @@ function ChatButton({ project_id, path, chatState }) {
9298
title={
9399
<span>
94100
<Icon name="comment" style={{ marginRight: "5px" }} />
95-
Hide or Show Document Chat
101+
<FormattedMessage
102+
id="chat.chat-indicator.tooltip"
103+
defaultMessage={"Hide or Show Document Chat"}
104+
/>
96105
</span>
97106
}
98107
placement={"leftTop"}
@@ -107,7 +116,9 @@ function ChatButton({ project_id, path, chatState }) {
107116
>
108117
<Icon name="comment" />
109118
<HiddenXS>
110-
<span style={{ marginLeft: "5px" }}>Chat</span>
119+
<span style={{ marginLeft: "5px" }}>
120+
{intl.formatMessage(labels.chat)}
121+
</span>
111122
</HiddenXS>
112123
</Button>
113124
</Tooltip>

src/packages/frontend/compute/api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export async function computeServerAction(opts: {
3737
await api("compute/compute-server-action", opts);
3838
}
3939

40+
export async function getServers(opts: { id?: number; project_id: string }) {
41+
return await api("compute/get-servers", opts);
42+
}
43+
4044
export async function getServerState(id: number) {
4145
return await api("compute/get-server-state", { id });
4246
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
Clone compute server config. Entirely done client side.
3+
4+
Main issue is DNS can't be the same.
5+
6+
In the future we will ALSO support a checkbox to clone the data too, but not yet.
7+
*/
8+
9+
import { Alert, Modal } from "antd";
10+
import { useState } from "react";
11+
import ShowError from "@cocalc/frontend/components/error";
12+
import Inline from "./inline";
13+
import { createServer, getServers } from "./api";
14+
15+
export default function Clone({ id, project_id, close }) {
16+
const [error, setError] = useState<string>("");
17+
const [loading, setLoading] = useState<boolean>(false);
18+
19+
return (
20+
<Modal
21+
open
22+
confirmLoading={loading}
23+
onCancel={close}
24+
onOk={async () => {
25+
try {
26+
setLoading(true);
27+
await createClone({ id, project_id });
28+
close();
29+
} catch (err) {
30+
setError(`${err}`);
31+
} finally {
32+
setLoading(false);
33+
}
34+
}}
35+
title={
36+
<>
37+
Clone Compute Server <Inline id={id} />
38+
</>
39+
}
40+
okText={
41+
<>
42+
Clon{loading ? "ing" : "e"} <Inline id={id} />
43+
</>
44+
}
45+
>
46+
<ShowError
47+
error={error}
48+
setError={setError}
49+
style={{ marginBottom: "15px" }}
50+
/>
51+
This makes a new deprovisioned compute server that is configured as close
52+
as possibleto this this compute server.{" "}
53+
<Alert
54+
showIcon
55+
style={{ margin: "15px" }}
56+
type="warning"
57+
message="The underlying disk is not copied."
58+
/>
59+
After cloning the compute server, you can edit anything about its
60+
configuration before starting it.
61+
</Modal>
62+
);
63+
}
64+
65+
async function createClone({
66+
id,
67+
project_id,
68+
}: {
69+
id: number;
70+
project_id: string;
71+
}) {
72+
const servers = await getServers({ project_id });
73+
const titles = new Set(servers.map((x) => x.title));
74+
const allDns = new Set(
75+
servers.filter((x) => x.configuration.dns).map((x) => x.configuration.dns),
76+
);
77+
let server;
78+
let done = false;
79+
for (const s of servers) {
80+
if (s.id == id) {
81+
server = s;
82+
done = true;
83+
break;
84+
}
85+
}
86+
if (!done) {
87+
throw Error(`no such compute server ${id}`);
88+
}
89+
let n = 1;
90+
let title = `Clone of ${server.title}`;
91+
if (titles.has(title)) {
92+
while (titles.has(title + ` (${n})`)) {
93+
n += 1;
94+
}
95+
title = title + ` (${n})`;
96+
}
97+
server.title = title;
98+
99+
delete server.configuration.authToken;
100+
101+
if (server.configuration.dns) {
102+
n = 1;
103+
while (allDns.has(server.configuration.dns + `-${n}`)) {
104+
n += 1;
105+
}
106+
server.configuration.dns = server.configuration.dns + `-${n}`;
107+
}
108+
109+
await createServer({ ...server });
110+
}

src/packages/frontend/compute/launcher.tsx

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Launcher buttons shown for a running compute server.
33
*/
44

5-
import { Button, Modal, Spin } from "antd";
5+
import { Button, Modal, Spin, Tooltip } from "antd";
66
import { Icon } from "@cocalc/frontend/components";
77
import { useImages } from "@cocalc/frontend/compute/images-hook";
88
import { useMemo, useState } from "react";
@@ -48,29 +48,46 @@ export default function Launcher({
4848
</Button>*/}
4949

5050
{apps["jupyterlab"] != null && (
51-
<Button
52-
onClick={() => setAppName("jupyterlab")}
53-
type="text"
54-
size="small"
55-
style={{ color: "#666" }}
56-
>
57-
<Icon
58-
name={apps["jupyterlab"].icon}
59-
style={{ marginRight: "-5px" }}
60-
/>
61-
JupyterLab
62-
</Button>
51+
<Tooltip title={apps["jupyterlab"].tip} placement="left">
52+
<Button
53+
onClick={() => setAppName("jupyterlab")}
54+
type="text"
55+
size="small"
56+
style={{ color: "#666" }}
57+
>
58+
<Icon
59+
name={apps["jupyterlab"].icon}
60+
style={{ marginRight: "-5px" }}
61+
/>
62+
JupyterLab
63+
</Button>
64+
</Tooltip>
6365
)}
6466
{apps["vscode"] != null && (
65-
<Button
66-
onClick={() => setAppName("vscode")}
67-
type="text"
68-
size="small"
69-
style={{ color: "#666" }}
70-
>
71-
<Icon name={apps["vscode"].icon} style={{ marginRight: "-5px" }} />
72-
VS Code
73-
</Button>
67+
<Tooltip title={apps["vscode"].tip} placement="left">
68+
<Button
69+
onClick={() => setAppName("vscode")}
70+
type="text"
71+
size="small"
72+
style={{ color: "#666" }}
73+
>
74+
<Icon name={apps["vscode"].icon} style={{ marginRight: "-5px" }} />
75+
VS Code
76+
</Button>
77+
</Tooltip>
78+
)}
79+
{apps["xpra"] != null && (
80+
<Tooltip title={apps["xpra"].tip} placement="left">
81+
<Button
82+
onClick={() => setAppName("xpra")}
83+
type="text"
84+
size="small"
85+
style={{ color: "#666" }}
86+
>
87+
<Icon name={apps["xpra"].icon} style={{ marginRight: "-5px" }} />
88+
Desktop
89+
</Button>
90+
</Tooltip>
7491
)}
7592
</div>
7693
);
@@ -151,7 +168,7 @@ export function AppLauncherModal({
151168
return <Spin />;
152169
}
153170
const image = server.configuration?.image ?? "defaults";
154-
const apps = IMAGES[image]?.apps ?? IMAGES["defaults"]?.apps ?? {};
171+
const apps = getApps(image);
155172

156173
return (
157174
<Modal
@@ -169,6 +186,7 @@ export function AppLauncherModal({
169186
onCancel={close}
170187
destroyOnClose
171188
>
189+
{apps[name]?.tip}
172190
<AppLauncher
173191
name={name}
174192
configuration={server.configuration}

src/packages/frontend/compute/menu.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { setServerConfiguration } from "@cocalc/frontend/compute/api";
1717
import ShowError from "@cocalc/frontend/components/error";
1818
import openSupportTab from "@cocalc/frontend/support/open";
1919
import { setTemplate } from "@cocalc/frontend/compute/api";
20+
import CloneModal from "./clone";
2021

2122
function getServer({ id, project_id }) {
2223
return redux
@@ -38,6 +39,12 @@ export function getApps(image) {
3839
if (IMAGES.getIn([image, "jupyterKernels"]) === false) {
3940
apps = { ...apps, jupyterlab: undefined };
4041
}
42+
if (apps["xpra"]) {
43+
if (!apps["xpra"].tip) {
44+
apps["xpra"].tip =
45+
"Launch an X11 Linux Graphical Desktop environment running directly on the compute server.";
46+
}
47+
}
4148
return apps;
4249
}
4350

@@ -103,7 +110,7 @@ function getItems({
103110
};
104111
const xpra = {
105112
key: "xpra",
106-
label: "X11 Desktop",
113+
label: "Desktop",
107114
icon: <Icon name="desktop" />,
108115
disabled:
109116
apps["xpra"] == null ||
@@ -253,6 +260,12 @@ function getItems({
253260
label: is_owner ? "Settings" : "Details...",
254261
};
255262

263+
const clone = {
264+
key: "clone",
265+
icon: <Icon name="copy" />,
266+
label: "Clone...",
267+
};
268+
256269
return [
257270
titleAndColor,
258271
// {
@@ -281,6 +294,7 @@ function getItems({
281294
},
282295
settings,
283296
options,
297+
clone,
284298
{
285299
type: "divider",
286300
},
@@ -433,6 +447,12 @@ export default function Menu({
433447
);
434448
break;
435449

450+
case "clone":
451+
setModal(
452+
<CloneModal id={id} project_id={project_id} close={close} />,
453+
);
454+
break;
455+
436456
case "serial-console-log":
437457
setModal(
438458
<SerialLogModal

0 commit comments

Comments
 (0)