-
Notifications
You must be signed in to change notification settings - Fork 550
Add ssh public keys on user-profile page #5223
Changes from 4 commits
fc8db98
c64fb10
963853e
c521801
29f3a22
aa22d17
84b9ff2
35ce9f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,6 +179,26 @@ export const getUserRequest = async username => { | |
| }); | ||
| }; | ||
|
|
||
| export const updateUserRequest = async (username, sskMessage) => { | ||
| const url = `${config.restServerUri}/api/v2/users/me`; | ||
| const token = checkToken(); | ||
| return fetchWrapper(url, { | ||
| method: 'PUT', | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| data: { | ||
| username: username, | ||
| extension: { | ||
| sshKeys: sskMessage, | ||
|
||
| }, | ||
| }, | ||
| patch: true, | ||
| }), | ||
| }); | ||
| }; | ||
|
|
||
| export const getTokenRequest = async () => { | ||
| return wrapper(() => client.token.getTokens()); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,14 +24,17 @@ import { | |
| listStorageDetailRequest, | ||
| getGroupsRequest, | ||
| updateBoundedClustersRequest, | ||
| updateUserRequest, | ||
| } from './conn'; | ||
|
|
||
| import t from '../../components/tachyons.scss'; | ||
| import { VirtualClusterDetailsList } from '../../home/home/virtual-cluster-statistics'; | ||
| import TokenList from './user-profile/token-list'; | ||
| import SSHlist from './user-profile/ssh-list'; | ||
| import UserProfileHeader from './user-profile/header'; | ||
| import StorageList from './user-profile/storage-list'; | ||
| import BoundedClusterDialog from './user-profile/bounded-cluster-dialog'; | ||
| import SSHListDialog from './user-profile/ssh-list-dialog'; | ||
| import BoundedClusterList from './user-profile/bounded-cluster-list'; | ||
|
|
||
| const UserProfileCard = ({ title, children, headerButton }) => { | ||
|
|
@@ -70,7 +73,11 @@ const UserProfile = () => { | |
| const [showBoundedClusterDialog, setShowBoundedClusterDialog] = useState( | ||
| false, | ||
| ); | ||
| const [showAddSSHpublicKeysDialog, setShowAddSSHpublicKeysDialog] = useState( | ||
| false, | ||
| ); | ||
| const [processing, setProcessing] = useState(false); | ||
| const [sshProcessing, setSSHProcessing] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const fetchData = async () => { | ||
|
|
@@ -139,6 +146,39 @@ const UserProfile = () => { | |
| }); | ||
| }); | ||
|
|
||
| // click `add public ssh keys button` -> open dialog | ||
| const onAddPublicKeys = useCallback(async sshPublicKeys => { | ||
| setSSHProcessing(true); | ||
| let updatedSSHPublickeys = []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please validate here: the user cannot add ssh keys with duplicate titles. (Title should be unique for one user)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| if (userInfo.extension.sshKeys) { | ||
| updatedSSHPublickeys = cloneDeep(userInfo.extension.sshKeys); | ||
| } | ||
| updatedSSHPublickeys.push({ | ||
| title: sshPublicKeys.title, | ||
| value: sshPublicKeys.value, | ||
| time: sshPublicKeys.time, | ||
| }); | ||
| await updateUserRequest(userInfo.username, updatedSSHPublickeys); | ||
| const updatedUserInfo = await getUserRequest(userInfo.username); | ||
| setUserInfo(updatedUserInfo); | ||
| setSSHProcessing(false); | ||
| }); | ||
|
|
||
| const onDeleteSSHkeys = useCallback(async sshPublicKeys => { | ||
| let updatedSSHPublickeys = []; | ||
| if (userInfo.extension.sshKeys) { | ||
| updatedSSHPublickeys = cloneDeep(userInfo.extension.sshKeys); | ||
| } | ||
| updatedSSHPublickeys = updatedSSHPublickeys.filter( | ||
| item => | ||
| item.title !== sshPublicKeys.title && | ||
|
||
| item.value !== sshPublicKeys.value, | ||
| ); | ||
| await updateUserRequest(userInfo.username, updatedSSHPublickeys); | ||
| const updatedUserInfo = await getUserRequest(userInfo.username); | ||
| setUserInfo(updatedUserInfo); | ||
| }); | ||
|
|
||
| const onRevokeToken = useCallback(async token => { | ||
| await revokeTokenRequest(token); | ||
| await getTokenRequest().then(res => setTokens(res.tokens)); | ||
|
|
@@ -197,6 +237,29 @@ const UserProfile = () => { | |
| onEditPassword={onEditPassword} | ||
| /> | ||
| </Card> | ||
| <UserProfileCard | ||
| title='SSH Public Keys' | ||
| headerButton={ | ||
| <DefaultButton | ||
| onClick={() => setShowAddSSHpublicKeysDialog(true)} | ||
| disabled={sshProcessing} | ||
| > | ||
| Add SSH Public Keys | ||
| </DefaultButton> | ||
| } | ||
| > | ||
| <SSHlist | ||
| sshKeys={userInfo.extension.sshKeys} | ||
| onDeleteSSHkeys={onDeleteSSHkeys} | ||
| /> | ||
| {/* dialog for add public ssh keys */} | ||
| {showAddSSHpublicKeysDialog && ( | ||
| <SSHListDialog | ||
| onDismiss={() => setShowAddSSHpublicKeysDialog(false)} | ||
| onAddPublickeys={onAddPublicKeys} | ||
| /> | ||
| )} | ||
| </UserProfileCard> | ||
| <UserProfileCard | ||
| title='Tokens' | ||
| headerButton={ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| import React, { useState } from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import { isEmpty } from 'lodash'; | ||
| import { | ||
| DefaultButton, | ||
| PrimaryButton, | ||
| DialogType, | ||
| Dialog, | ||
| DialogFooter, | ||
| TextField, | ||
| } from 'office-ui-fabric-react'; | ||
|
|
||
| import t from '../../../components/tachyons.scss'; | ||
|
|
||
| const SSHListDialog = ({ onDismiss, onAddPublickeys }) => { | ||
| const [error, setError] = useState(''); | ||
| const [inputTitleError, setInputTitleError] = useState(''); | ||
| const [inputValueError, setInputValueError] = useState(''); | ||
| const [processing, setProcessing] = useState(false); | ||
| const [title, setTitle] = useState(''); | ||
| const [value, setValue] = useState(''); | ||
|
|
||
| const onAddAsync = async () => { | ||
| if (title.trim() === '') { | ||
| setInputTitleError('Please input title'); | ||
| } else if (value.trim() === '') { | ||
| setInputValueError('Please input SSH value'); | ||
| } else { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please validate the You can use this regex:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| setProcessing(true); | ||
| try { | ||
| await onAddPublickeys({ | ||
| title: title.trim(), | ||
| value: value.trim(), | ||
| time: new Date().getTime(), | ||
| }); | ||
| } catch (error) { | ||
| setError(error.message); | ||
| } finally { | ||
| setProcessing(false); | ||
| onDismiss(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <Dialog | ||
| hidden={false} | ||
| onDismiss={onDismiss} | ||
| dialogContentProps={{ | ||
| type: DialogType.normal, | ||
| title: 'Add SSH Public Keys', | ||
| }} | ||
| modalProps={{ | ||
| isBlocking: true, | ||
| }} | ||
| minWidth={600} | ||
| > | ||
| <div> | ||
| <div className={t.mt1}> | ||
| <TextField | ||
| label='title' | ||
|
||
| required={true} | ||
| errorMessage={inputTitleError} | ||
| onChange={e => { | ||
| setTitle(e.target.value); | ||
| setInputTitleError(null); | ||
| }} | ||
| validateOnFocusOut={true} | ||
| /> | ||
| </div> | ||
| <div className={t.mt1}> | ||
| <TextField | ||
| label='value' | ||
|
||
| required={true} | ||
| errorMessage={inputValueError} | ||
| onChange={e => { | ||
| setValue(e.target.value); | ||
| setInputValueError(null); | ||
| }} | ||
| multiline | ||
| rows={5} | ||
| validateOnFocusOut={true} | ||
| /> | ||
| </div> | ||
| </div> | ||
| <DialogFooter> | ||
| <PrimaryButton | ||
| onClick={onAddAsync} | ||
| disabled={processing} | ||
| text='Add' | ||
| /> | ||
| <DefaultButton | ||
| onClick={onDismiss} | ||
| disabled={processing} | ||
| text='Cancel' | ||
| /> | ||
| </DialogFooter> | ||
| </Dialog> | ||
| <Dialog | ||
| hidden={isEmpty(error)} | ||
| onDismiss={() => setError('')} | ||
| dialogContentProps={{ | ||
| type: DialogType.normal, | ||
| title: 'Error', | ||
| subText: error, | ||
| }} | ||
| modalProps={{ | ||
| isBlocking: true, | ||
| }} | ||
| > | ||
| <DialogFooter> | ||
| <DefaultButton onClick={() => setError('')}>OK</DefaultButton> | ||
| </DialogFooter> | ||
| </Dialog> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| SSHListDialog.propTypes = { | ||
| onDismiss: PropTypes.func.isRequired, | ||
| onAddPublickeys: PropTypes.func.isRequired, | ||
| }; | ||
|
|
||
| export default SSHListDialog; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sskMessage->sshMessage?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated