Skip to content

Commit c30716d

Browse files
mustard-mhiQQBot
authored andcommitted
[dashboard] support ssh copy-paste with ssh keys
1 parent 7d2450b commit c30716d

File tree

2 files changed

+91
-38
lines changed

2 files changed

+91
-38
lines changed

components/dashboard/src/components/Modal.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default function Modal(props: {
1313
// specify a key if having the same title and window.location
1414
specify?: string;
1515
title?: string;
16+
hideDivider?: boolean;
1617
buttons?: React.ReactChild[] | React.ReactChild;
1718
children: React.ReactChild[] | React.ReactChild;
1819
visible: boolean;
@@ -94,7 +95,12 @@ export default function Modal(props: {
9495
{props.title ? (
9596
<>
9697
<h3 className="pb-2">{props.title}</h3>
97-
<div className="border-t border-b border-gray-200 dark:border-gray-800 mt-2 -mx-6 px-6 py-4">
98+
<div
99+
className={
100+
"border-gray-200 dark:border-gray-800 -mx-6 px-6 " +
101+
(props.hideDivider ? "" : "border-t border-b mt-2 py-4")
102+
}
103+
>
98104
{props.children}
99105
</div>
100106
<div className="flex justify-end mt-6 space-x-2">{props.buttons}</div>

components/dashboard/src/workspaces/ConnectToSSHModal.tsx

+84-37
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { useState } from "react";
7+
import { useEffect, useState } from "react";
88
import Modal from "../components/Modal";
99
import Tooltip from "../components/Tooltip";
1010
import copy from "../images/copy.svg";
11-
import AlertBox from "../components/AlertBox";
12-
import InfoBox from "../components/InfoBox";
11+
import Alert from "../components/Alert";
12+
import TabMenuItem from "../components/TabMenuItem";
13+
import { settingsPathSSHKeys } from "../settings/settings.routes";
14+
import { getGitpodService } from "../service/service";
1315

1416
function InputWithCopy(props: { value: string; tip?: string; className?: string }) {
1517
const [copied, setCopied] = useState<boolean>(false);
@@ -35,7 +37,7 @@ function InputWithCopy(props: { value: string; tip?: string; className?: string
3537
autoFocus
3638
className="w-full pr-8 overscroll-none"
3739
type="text"
38-
defaultValue={props.value}
40+
value={props.value}
3941
/>
4042
<div className="cursor-pointer" onClick={() => copyToClipboard(props.value)}>
4143
<div className="absolute top-1/3 right-3">
@@ -55,40 +57,80 @@ interface SSHProps {
5557
}
5658

5759
function SSHView(props: SSHProps) {
58-
const sshCommand = `ssh '${props.workspaceId}#${props.ownerToken}@${props.ideUrl.replace(
59-
props.workspaceId,
60-
props.workspaceId + ".ssh",
61-
)}'`;
60+
const [hasSSHKey, setHasSSHKey] = useState(true);
61+
const [selectSSHKey, setSelectSSHKey] = useState(true);
62+
63+
useEffect(() => {
64+
getGitpodService()
65+
.server.hasSSHPublicKey()
66+
.then((d) => {
67+
setHasSSHKey(d);
68+
})
69+
.catch(console.error);
70+
}, []);
71+
72+
const host = props.ideUrl.replace(props.workspaceId, props.workspaceId + ".ssh");
73+
const sshAccessTokenCommand = `ssh '${props.workspaceId}#${props.ownerToken}@${host}'`;
74+
const sshKeyCommand = `ssh '${props.workspaceId}@${host}'`;
75+
6276
return (
63-
<div className="border-t border-b border-gray-200 dark:border-gray-800 mt-2 -mx-6 px-6 py-6">
64-
<div className="mt-1 mb-4">
65-
<AlertBox>
66-
<p className="text-red-500 whitespace-normal text-base">
77+
<>
78+
<div className="flex flex-row">
79+
<TabMenuItem
80+
key="ssh_key"
81+
name="SSH Key"
82+
selected={selectSSHKey}
83+
onClick={() => {
84+
setSelectSSHKey(true);
85+
}}
86+
/>
87+
<TabMenuItem
88+
key="access_token"
89+
name="Access Token"
90+
selected={!selectSSHKey}
91+
onClick={() => {
92+
setSelectSSHKey(false);
93+
}}
94+
/>
95+
</div>
96+
<div className="border-gray-200 dark:border-gray-800 border-b"></div>
97+
<div className="space-y-4 mt-4">
98+
{!selectSSHKey && (
99+
<Alert type="warning" className="whitespace-normal">
67100
<b>Anyone</b> on the internet with this command can access the running workspace. The command
68101
includes a generated access token that resets on every workspace restart.
69-
</p>
70-
</AlertBox>
71-
<InfoBox className="mt-4">
72-
<p className="text-gray-500 whitespace-normal text-base">
73-
Before connecting via SSH, make sure you have an existing SSH private key on your machine. You
74-
can create one using&nbsp;
75-
<a
76-
href="https://en.wikipedia.org/wiki/Ssh-keygen"
77-
target="_blank"
78-
rel="noopener noreferrer"
79-
className="gp-link"
80-
>
81-
ssh-keygen
102+
</Alert>
103+
)}
104+
{!hasSSHKey && selectSSHKey && (
105+
<Alert type="warning" className="whitespace-normal">
106+
You don't have any public SSH keys in your Gitpod account. You can{" "}
107+
<a href={settingsPathSSHKeys} target="setting-keys" className="gp-link">
108+
add a new public key
82109
</a>
83-
.
84-
</p>
85-
</InfoBox>
86-
<p className="mt-4 text-gray-500 whitespace-normal text-base">
87-
The following shell command can be used to SSH into this workspace.
110+
, or use a generated access token.
111+
</Alert>
112+
)}
113+
114+
<p className="text-gray-500 whitespace-normal text-base">
115+
{!selectSSHKey ? (
116+
"The following shell command can be used to SSH into this workspace."
117+
) : (
118+
<>
119+
The following shell command can be used to SSH into this workspace with a{" "}
120+
<a href={settingsPathSSHKeys} target="setting-keys" className="gp-link">
121+
ssh key
122+
</a>
123+
.
124+
</>
125+
)}
88126
</p>
89127
</div>
90-
<InputWithCopy value={sshCommand} tip="Copy SSH Command" />
91-
</div>
128+
<InputWithCopy
129+
className="my-2"
130+
value={!selectSSHKey ? sshAccessTokenCommand : sshKeyCommand}
131+
tip="Copy SSH Command"
132+
/>
133+
</>
92134
);
93135
}
94136

@@ -99,14 +141,19 @@ export default function ConnectToSSHModal(props: {
99141
onClose: () => void;
100142
}) {
101143
return (
102-
// TODO: Use title and buttons props
103-
<Modal visible={true} onClose={props.onClose}>
104-
<h3 className="mb-4">Connect via SSH</h3>
105-
<SSHView workspaceId={props.workspaceId} ownerToken={props.ownerToken} ideUrl={props.ideUrl} />
106-
<div className="flex justify-end mt-6">
144+
<Modal
145+
title="Connect via SSH"
146+
hideDivider
147+
buttons={
107148
<button className={"ml-2 secondary"} onClick={() => props.onClose()}>
108149
Close
109150
</button>
151+
}
152+
visible={true}
153+
onClose={props.onClose}
154+
>
155+
<div className="border-gray-200 dark:border-gray-800 -mx-6 px-6 border-b pb-4">
156+
<SSHView workspaceId={props.workspaceId} ownerToken={props.ownerToken} ideUrl={props.ideUrl} />
110157
</div>
111158
</Modal>
112159
);

0 commit comments

Comments
 (0)