-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[dashboard] ssh keys setting support
- Loading branch information
1 parent
1ebde98
commit 56aa88c
Showing
9 changed files
with
331 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
/** | ||
* Copyright (c) 2022 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License-AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { useContext, useEffect, useState } from "react"; | ||
import { PageWithSubMenu } from "../components/PageWithSubMenu"; | ||
import getSettingsMenu from "./settings-menu"; | ||
import { PaymentContext } from "../payment-context"; | ||
import Modal from "../components/Modal"; | ||
import Alert from "../components/Alert"; | ||
import { Item, ItemField, ItemFieldContextMenu } from "../components/ItemsList"; | ||
import ConfirmationModal from "../components/ConfirmationModal"; | ||
import { SSHPublicKeyValue, UserSSHPublicKeyValue } from "@gitpod/gitpod-protocol"; | ||
import { getGitpodService } from "../service/service"; | ||
|
||
interface AddModalProps { | ||
value: SSHPublicKeyValue; | ||
onClose: () => void; | ||
onSave: () => void; | ||
} | ||
|
||
interface DeleteModalProps { | ||
value: KeyData; | ||
onConfirm: () => void; | ||
onClose: () => void; | ||
} | ||
|
||
type KeyData = SSHPublicKeyValue & { id?: string }; | ||
|
||
export function AddSSHKeyModal(props: AddModalProps) { | ||
const [errorMsg, setErrorMsg] = useState(""); | ||
|
||
const [value, setValue] = useState({ ...props.value }); | ||
const update = (pev: Partial<SSHPublicKeyValue>) => { | ||
setValue({ ...value, ...pev }); | ||
setErrorMsg(""); | ||
}; | ||
|
||
useEffect(() => { | ||
setValue({ ...props.value }); | ||
setErrorMsg(""); | ||
}, [props.value]); | ||
|
||
const save = async () => { | ||
const tmp = SSHPublicKeyValue.validate(value); | ||
if (tmp) { | ||
setErrorMsg(tmp); | ||
return false; | ||
} | ||
try { | ||
await getGitpodService().server.addSSHPublicKey(value); | ||
} catch (e) { | ||
setErrorMsg(e.message); | ||
return false; | ||
} | ||
props.onClose(); | ||
props.onSave(); | ||
return true; | ||
}; | ||
|
||
return ( | ||
<Modal | ||
title="New SSH Key" | ||
buttons={ | ||
<button className="ml-2" onClick={save}> | ||
Add Key | ||
</button> | ||
} | ||
visible={true} | ||
onClose={props.onClose} | ||
onEnter={save} | ||
> | ||
<> | ||
{errorMsg.length > 0 && ( | ||
<Alert type="error" className="mb-2"> | ||
{errorMsg} | ||
</Alert> | ||
)} | ||
</> | ||
<div className="text-gray-500 dark:text-gray-400 text-md"> | ||
Add an SSH key for secure access workspaces via SSH. Learn more | ||
</div> | ||
<Alert type="info" className="mt-2"> | ||
SSH key are used to connect securely to workspaces.{" "} | ||
<a | ||
href="https://www.gitpod.io/docs/configure/ssh#create-an-ssh-key" | ||
target="gitpod-create-ssh-key-doc" | ||
className="gp-link" | ||
> | ||
Learn how to create an SSH Key | ||
</a> | ||
</Alert> | ||
<div className="mt-2"> | ||
<h4>Key</h4> | ||
<textarea | ||
autoFocus | ||
style={{ height: "160px" }} | ||
className="w-full resize-none" | ||
value={value.key} | ||
placeholder="Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', | ||
'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', | ||
'ssh-ed25519', | ||
'[email protected]', or | ||
'[email protected]'" | ||
onChange={(v) => update({ key: v.target.value })} | ||
/> | ||
</div> | ||
<div className="mt-2"> | ||
<h4>Title</h4> | ||
<input | ||
className="w-full" | ||
type="text" | ||
placeholder="e.g. laptop" | ||
value={value.name} | ||
onChange={(v) => { | ||
update({ name: v.target.value }); | ||
}} | ||
/> | ||
</div> | ||
</Modal> | ||
); | ||
} | ||
|
||
export function DeleteSSHKeyModal(props: DeleteModalProps) { | ||
const confirmDelete = async () => { | ||
await getGitpodService().server.deleteSSHPublicKey(props.value.id!); | ||
props.onConfirm(); | ||
props.onClose(); | ||
}; | ||
return ( | ||
<ConfirmationModal | ||
title="Delete SSH Key" | ||
areYouSureText="This action CANNOT be undone. This will permanently delete the SSH key and if you'd like to use it in the future, you will need to upload it again." | ||
buttonText="Delete SSH Key" | ||
onClose={props.onClose} | ||
onConfirm={confirmDelete} | ||
/> | ||
); | ||
} | ||
|
||
export default function SSHKeys() { | ||
const { showPaymentUI, showUsageBasedUI } = useContext(PaymentContext); | ||
|
||
const [dataList, setDataList] = useState<UserSSHPublicKeyValue[]>([]); | ||
const [currentData, setCurrentData] = useState<KeyData>({ name: "", key: "" }); | ||
const [showAddModal, setShowAddModal] = useState(false); | ||
const [showDelModal, setShowDelModal] = useState(false); | ||
|
||
const loadData = () => { | ||
getGitpodService() | ||
.server.getSSHPublicKeys() | ||
.then((r) => setDataList(r)); | ||
}; | ||
|
||
useEffect(() => { | ||
loadData(); | ||
}, []); | ||
|
||
const addOne = () => { | ||
setCurrentData({ name: "", key: "" }); | ||
setShowAddModal(true); | ||
setShowDelModal(false); | ||
}; | ||
|
||
const deleteOne = (value: UserSSHPublicKeyValue) => { | ||
setCurrentData({ id: value.id, name: value.name, key: "" }); | ||
setShowAddModal(false); | ||
setShowDelModal(true); | ||
}; | ||
|
||
return ( | ||
<PageWithSubMenu | ||
subMenu={getSettingsMenu({ showPaymentUI, showUsageBasedUI })} | ||
title="SSH Keys" | ||
subtitle="Connect securely to workspaces." | ||
> | ||
{showAddModal && ( | ||
<AddSSHKeyModal value={currentData} onSave={loadData} onClose={() => setShowAddModal(false)} /> | ||
)} | ||
{showDelModal && ( | ||
<DeleteSSHKeyModal value={currentData} onConfirm={loadData} onClose={() => setShowDelModal(false)} /> | ||
)} | ||
<div className="flex items-start sm:justify-between mb-2"> | ||
<div> | ||
<h3>SSH Keys</h3> | ||
<h2 className="text-gray-500"> | ||
Create and manage SSH keys.{" "} | ||
<a href="https://www.gitpod.io/docs/configure/ssh" target="gitpod-ssh-doc" className="gp-link"> | ||
More details | ||
</a> | ||
</h2> | ||
</div> | ||
{dataList.length !== 0 ? ( | ||
<div className="mt-3 flex"> | ||
<button onClick={addOne} className="ml-2"> | ||
New SSH Key | ||
</button> | ||
</div> | ||
) : null} | ||
</div> | ||
{dataList.length === 0 ? ( | ||
<div className="bg-gray-100 dark:bg-gray-800 rounded-xl w-full h-96"> | ||
<div className="pt-28 flex flex-col items-center w-120 m-auto"> | ||
<h3 className="text-center pb-3 text-gray-500 dark:text-gray-400">No SSH Keys</h3> | ||
<div className="text-center pb-6 text-gray-500"> | ||
SSH keys allow you to establish a <b>secure connection</b> between your <b>computer</b> and{" "} | ||
<b>workspaces</b>. | ||
</div> | ||
<button onClick={addOne}>New SSH Key</button> | ||
</div> | ||
</div> | ||
) : ( | ||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4"> | ||
{dataList.map((key) => { | ||
return ( | ||
<Item solid className="items-start"> | ||
<ItemField className="flex flex-col gap-y box-border overflow-hidden"> | ||
<p className="truncate text-gray-400 dark:text-gray-600"> | ||
{key.fingerprint | ||
.toUpperCase() | ||
.match(/.{1,2}/g) | ||
?.join(":") ?? key.fingerprint} | ||
</p> | ||
<div className="truncate my-1 text-xl text-gray-800 dark:text-gray-100 font-semibold"> | ||
{key.name} | ||
</div> | ||
<p className="truncate mt-4">Added on {key.creationTime}</p> | ||
{!!key.lastUsedTime && <p className="truncate">Last used on {key.lastUsedTime}</p>} | ||
</ItemField> | ||
<ItemFieldContextMenu | ||
position="start" | ||
menuEntries={[ | ||
{ | ||
title: "Delete", | ||
customFontStyle: | ||
"text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300", | ||
onClick: () => deleteOne(key), | ||
}, | ||
]} | ||
/> | ||
</Item> | ||
); | ||
})} | ||
</div> | ||
)} | ||
</PageWithSubMenu> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.