Skip to content

Commit

Permalink
feat: [code-2061]: add ssh support in gitness (harness#2140)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjlee01 authored and Harness committed Jul 1, 2024
1 parent baa31cc commit 6a6c62f
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 36 deletions.
72 changes: 49 additions & 23 deletions web/src/components/CloneButtonTooltip/CloneButtonTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import React, { useState } from 'react'
import { Render } from 'react-jsx-match'
import cx from 'classnames'
import { Button, ButtonVariation, Container, FlexExpander, Layout, Text } from '@harnessio/uicore'
import { Button, ButtonVariation, Container, FlexExpander, Layout, PillToggle, Text } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { Classes } from '@blueprintjs/core'
import { Icon } from '@harnessio/icons'
Expand All @@ -32,11 +32,16 @@ interface CloneButtonTooltipProps {
httpsURL: string
sshURL: string
}
enum CloneType {
HTTPS = 'https',
SSH = 'ssh'
}

export function CloneButtonTooltip({ httpsURL, sshURL }: CloneButtonTooltipProps) {
const { getString } = useStrings()
const [flag, setFlag] = useState(false)
const { isCurrentSessionPublic } = useAppContext()
const [type, setType] = useState(CloneType.HTTPS)
return (
<Container className={css.container} padding="xlarge">
<Layout.Vertical spacing="small">
Expand All @@ -53,30 +58,51 @@ export function CloneButtonTooltip({ httpsURL, sshURL }: CloneButtonTooltipProps
/>
</Container>
<Text font={{ variation: FontVariation.H4 }}>{getString('cloneHTTPS')}</Text>
<Container padding={{ top: 'small' }}>
{
// TODO: replace with data from config api
sshURL && <Text font={{ variation: FontVariation.BODY2_SEMI }}>{getString('http')}</Text>
}
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{httpsURL}</Text>
{sshURL ? (
<Container padding={{ top: 'small' }}>
<Layout.Vertical>
<Container padding={{ bottom: 'medium' }}>
<PillToggle
selectedView={type}
onChange={typeClicked => {
setType(typeClicked as CloneType)
}}
options={[
{ label: CloneType.HTTPS.toUpperCase(), value: 'https' },
{ label: CloneType.SSH.toUpperCase(), value: 'ssh' }
]}
/>
</Container>
{type === CloneType.HTTPS ? (
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{httpsURL}</Text>

<CopyButton content={httpsURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal>
</Container>
{
// TODO: replace with data from config api
sshURL && (
<Container padding={{ top: 'small' }}>
<Text font={{ variation: FontVariation.BODY2_SEMI }}>{getString('ssh')}</Text>
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{sshURL}</Text>
<CopyButton
content={httpsURL}
id={css.cloneCopyButton}
icon={CodeIcon.Copy}
iconProps={{ size: 14 }}
/>
</Layout.Horizontal>
) : (
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{sshURL}</Text>

<CopyButton content={sshURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal>
)}
</Layout.Vertical>
</Container>
) : (
<Container padding={{ top: 'small' }}>
<Layout.Horizontal className={css.layout}>
<Text className={css.url}>{httpsURL}</Text>

<CopyButton content={httpsURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal>
</Container>
)}

<CopyButton content={sshURL} id={css.cloneCopyButton} icon={CodeIcon.Copy} iconProps={{ size: 14 }} />
</Layout.Horizontal>
</Container>
)
}
<Render when={!isCurrentSessionPublic}>
<Button
width={300}
Expand Down
13 changes: 13 additions & 0 deletions web/src/framework/strings/stringTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,19 @@ export interface StringsMap {
spaces: string
squashMerge: string
ssh: string
'sshCard.addedOn': string
'sshCard.beginsWithContent': string
'sshCard.deleteSshMsg': string
'sshCard.deleteSshTitle': string
'sshCard.mySshKeys': string
'sshCard.newSshKey': string
'sshCard.noSshKeyText': string
'sshCard.noTokensText': string
'sshCard.personalAccessToken': string
'sshCard.publicKey': string
'sshCard.sshContent': string
'sshCard.sshkey': string
'sshCard.successSshKeyMsg': string
sslVerificationLabel: string
startSearching: string
status: string
Expand Down
15 changes: 15 additions & 0 deletions web/src/i18n/strings.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1219,3 +1219,18 @@ cmdlineInfo:
stepFourSub: See Resolving a merge conflict using the command line for step-by-step instruction on resolving merge conflicts
stepFive: Push the changes
stepFiveSub: git push -u origin {source}
sshCard:
newSshKey: New SSH Key
sshkey: SSH Key
publicKey: Public Key
noSshKeyText: There are no SSH Keys associated with this account
mySshKeys: My SSH Keys
sshContent: SSH keys allow you to establish a secure connection between your computer and GitLab. SSH fingerprints
verify if the client is connecting to the correct host. {{learnMore}}
beginsWithContent: Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'.
deleteSshTitle: Delete SSH Key
deleteSshMsg: Are you sure you want to delete this SSH Key?
successSshKeyMsg: SSH Key deleted successfully
addedOn: Added On
personalAccessToken: Personal Access Token
noTokensText: There are no personal access tokens associated with this account
1 change: 1 addition & 0 deletions web/src/icons/sshKey.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 142 additions & 0 deletions web/src/pages/UserProfile/NewSshKey/NewSshKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2023 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { useState } from 'react'
import { Button, ButtonVariation, Dialog, FormikForm, FormInput, Layout, Text, useToaster } from '@harnessio/uicore'
import { Formik } from 'formik'
import { useMutate } from 'restful-react'
import * as Yup from 'yup'

import { PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
import { useModalHook } from 'hooks/useModalHook'
import { useStrings } from 'framework/strings'
import { EnumPublicKeyUsage, REGEX_VALID_REPO_NAME, getErrorMessage } from 'utils/Utils'

import { FormInputWithCopyButton } from 'components/UserManagementFlows/AddUserModal'

import css from '../UserProfile.module.scss'

interface userSshKeyRequest {
content: string
identifier: string
usage: EnumPublicKeyUsage
}
const useNewSshKey = ({ onClose }: { onClose: () => void }) => {
const { getString } = useStrings()
const { mutate } = useMutate({ path: '/api/v1/user/keys', verb: 'POST' })
const { showError } = useToaster()

const [generatedToken, setGeneratedToken] = useState<string>()
const isTokenGenerated = Boolean(generatedToken)

// const lifeTimeOptions: SelectOption[] = useMemo(
// () => [
// { label: getString('nDays', { number: 7 }), value: 604800000000000 },
// { label: getString('nDays', { number: 30 }), value: 2592000000000000 },
// { label: getString('nDays', { number: 60 }), value: 5184000000000000 },
// { label: getString('nDays', { number: 90 }), value: 7776000000000000 },
// { label: getString('noExpiration'), value: Infinity }
// ],
// [getString]
// )

const onModalClose = () => {
setGeneratedToken('')
hideSshKeyModal()
onClose()
}

const [openSshKeyModal, hideSshKeyModal] = useModalHook(() => {
return (
<Dialog isOpen enforceFocus={false} onClose={onModalClose} title={getString('sshCard.newSshKey')}>
<Formik<userSshKeyRequest>
initialValues={{
identifier: '',
content: '',
usage: 'auth'
}}
validationSchema={Yup.object().shape({
identifier: Yup.string()
.required(getString('validation.nameIsRequired'))
.matches(REGEX_VALID_REPO_NAME, getString('validation.nameInvalid'))
})}
onSubmit={async values => {
const payload = { ...values }
mutate(payload)
.then(() => {
hideSshKeyModal()
onClose()
})
.catch(err => {
showError(getErrorMessage(err))
})
}}>
{() => {
return (
<FormikForm>
<FormInputWithCopyButton
name="identifier"
label={getString('sshCard.newSshKey')}
placeholder={getString('newToken.namePlaceholder')}
disabled={isTokenGenerated}
/>
<FormInput.TextArea
label={
<Layout.Horizontal
className={css.textAreaContainer}
flex={{ justifyContent: 'flex-start', alignItems: 'flex-start' }}>
<Text>{getString('sshCard.publicKey')}</Text>

<Text
padding={{ left: 'small' }}
className={css.icon}
icon="code-info"
tooltip={getString('sshCard.beginsWithContent')}
tooltipProps={{
isDark: true,
interactionKind: PopoverInteractionKind.HOVER,
position: PopoverPosition.BOTTOM
}}
iconProps={{ size: 16 }}
/>
</Layout.Horizontal>
}
name="content"
/>

<Layout.Horizontal margin={{ top: 'xxxlarge' }} spacing="medium">
<Button
text={getString('newToken.generateToken')}
type="submit"
variation={ButtonVariation.PRIMARY}
/>
<Button text={getString('cancel')} onClick={hideSshKeyModal} variation={ButtonVariation.TERTIARY} />
</Layout.Horizontal>
</FormikForm>
)
}}
</Formik>
</Dialog>
)
}, [generatedToken])

return {
openSshKeyModal,
hideSshKeyModal
}
}

export default useNewSshKey
44 changes: 44 additions & 0 deletions web/src/pages/UserProfile/UserProfile.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,49 @@
width: 100%;
margin-top: var(--spacing-medium);
}
.containerCard {
background-color: var(--grey-0) !important;
padding: var(--spacing-large) var(--spacing-xlarge) !important;
border-radius: 4px;
box-shadow: 0px 0.5px 2px 0px rgba(96, 97, 112, 0.16), 0px 0px 1px 0px rgba(40, 41, 61, 0.08);
}
}
}

.icon {
> svg {
fill: var(--primary-7) !important;

> path {
fill: var(--primary-7) !important;
}
}
}

.popover {
padding: var(--spacing-medium) !important;
}

.textAreaContainer {
:global {
.bp3-popover-wrapper {
margin-top: 2px !important;
.bp3-icon {
> svg {
fill: var(--primary-7) !important;

> path {
fill: var(--primary-7) !important;
}
}
}
}
}
}

.iconContainer {
margin-top: 8px !important;
background: var(--primary-2) !important;
border-radius: 4px !important;
margin-right: var(--spacing-3) !important;
}
5 changes: 5 additions & 0 deletions web/src/pages/UserProfile/UserProfile.module.scss.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@
/* eslint-disable */
// This is an auto-generated file
export declare const avatar: string
export declare const containerCard: string
export declare const detailField: string
export declare const detailsCtn: string
export declare const editableTextWrapper: string
export declare const icon: string
export declare const iconContainer: string
export declare const logoutCard: string
export declare const mainCtn: string
export declare const pageCtn: string
export declare const popover: string
export declare const profileCard: string
export declare const table: string
export declare const textAreaContainer: string
export declare const textInput: string
Loading

0 comments on commit 6a6c62f

Please sign in to comment.