mirror of https://github.com/harness/drone.git
feat: [AH-801]: Support ECR source in upstream proxy flow (#3177)
* feat: [AH-801] handle accessKeyType for gitness * feat: [AH-801] update key for gitness * feat: [AH-801]: support description and labels in upstream porxy create and update form * feat: [AH-801]: fix breaking css from gitness * feat: [AH-801]: implement MultiTypeSecretInput in HAR * feat: [AH-801]: support ecr source in opensource * feat: [AH-801]: Support ECR source in upstream proxy flowBT-10437
parent
016a0ce2f5
commit
f4dfe93677
|
@ -51,7 +51,7 @@
|
|||
"@codemirror/view": "^6.9.6",
|
||||
"@harnessio/design-system": "^2.1.1",
|
||||
"@harnessio/icons": "^2.1.9",
|
||||
"@harnessio/react-har-service-client": "^0.3.0",
|
||||
"@harnessio/react-har-service-client": "^0.4.0",
|
||||
"@harnessio/react-ssca-manager-client": "^0.65.0",
|
||||
"@harnessio/uicore": "^4.1.2",
|
||||
"@tanstack/react-query": "4.20.4",
|
||||
|
|
|
@ -23,7 +23,7 @@ interface SecretFormInputProps<T> {
|
|||
name: string
|
||||
scope: Scope
|
||||
spaceIdFieldName: string
|
||||
label: React.ReactNode
|
||||
label?: React.ReactNode
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
formik?: FormikProps<T>
|
||||
|
|
|
@ -21,4 +21,5 @@
|
|||
|
||||
:root {
|
||||
--har-table-name-column-min-width: 250px;
|
||||
--input-element-width: 580px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
.labelContainer {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdownSelect {
|
||||
margin-bottom: var(--spacing-xsmall);
|
||||
}
|
20
web/src/ar/components/MultiTypeSecretInput/MultiTypeSecretInput.module.scss.d.ts
vendored
Normal file
20
web/src/ar/components/MultiTypeSecretInput/MultiTypeSecretInput.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const dropdownSelect: string
|
||||
export declare const labelContainer: string
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright 2024 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, { useEffect } from 'react'
|
||||
import { get } from 'lodash-es'
|
||||
import classNames from 'classnames'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { FormGroup } from '@blueprintjs/core'
|
||||
import { Container, FormInput, Label, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { useAppStore, useParentComponents } from '@ar/hooks'
|
||||
|
||||
import css from './MultiTypeSecretInput.module.scss'
|
||||
|
||||
export enum SecretValueType {
|
||||
TEXT = 'TEXT',
|
||||
ENCRYPTED = 'ENCRYPTED'
|
||||
}
|
||||
|
||||
interface MultiTypeSecretInputProps {
|
||||
name: string
|
||||
label: string
|
||||
typeField: string
|
||||
secretField: string
|
||||
secretSpaceIdField: string
|
||||
helperText?: string
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
placeholder?: string
|
||||
disabled?: boolean
|
||||
onChangeType?: (type: SecretValueType) => void
|
||||
onChangeValue?: (e: React.FormEvent<HTMLElement>, type: SecretValueType) => void
|
||||
}
|
||||
|
||||
export default function MultiTypeSecretInput(props: MultiTypeSecretInputProps) {
|
||||
const {
|
||||
name,
|
||||
className,
|
||||
labelClassName,
|
||||
label,
|
||||
typeField,
|
||||
onChangeType,
|
||||
onChangeValue,
|
||||
placeholder,
|
||||
disabled,
|
||||
secretField,
|
||||
secretSpaceIdField
|
||||
} = props
|
||||
|
||||
const formik = useFormikContext()
|
||||
const valueType = get(formik.values, typeField)
|
||||
|
||||
const { scope } = useAppStore()
|
||||
const { getString } = useStrings()
|
||||
const { SecretFormInput } = useParentComponents()
|
||||
|
||||
useEffect(() => {
|
||||
if (!valueType) {
|
||||
formik.setFieldValue(typeField, SecretValueType.TEXT)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<Layout.Vertical className={className}>
|
||||
<Layout.Horizontal className={classNames(labelClassName, css.labelContainer)}>
|
||||
<Label>{label}</Label>
|
||||
<FormInput.DropDown
|
||||
className={css.dropdownSelect}
|
||||
name={typeField}
|
||||
items={[
|
||||
{ label: getString('plaintext'), value: SecretValueType.TEXT },
|
||||
{ label: getString('encrypted'), value: SecretValueType.ENCRYPTED }
|
||||
]}
|
||||
onChange={option => {
|
||||
onChangeType?.(option.value as SecretValueType)
|
||||
formik.setFieldValue(name, undefined)
|
||||
formik.setFieldValue(secretField, undefined)
|
||||
formik.setFieldValue(secretSpaceIdField, undefined)
|
||||
}}
|
||||
dropDownProps={{
|
||||
isLabel: true,
|
||||
filterable: false,
|
||||
minWidth: 'unset'
|
||||
}}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
<Container>
|
||||
{valueType === SecretValueType.TEXT && (
|
||||
<FormInput.Text
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
onChange={e => onChangeValue?.(e, valueType)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
{valueType === SecretValueType.ENCRYPTED && (
|
||||
<SecretFormInput
|
||||
name={secretField}
|
||||
spaceIdFieldName={secretSpaceIdField}
|
||||
placeholder={placeholder}
|
||||
scope={scope}
|
||||
disabled={disabled}
|
||||
formik={formik}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</FormGroup>
|
||||
)
|
||||
}
|
|
@ -79,7 +79,7 @@ export const Tags = (props: TagsComponentProps): JSX.Element => {
|
|||
}, [hasValue])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container margin={{ bottom: isTagsOpen ? 'none' : 'small' }}>
|
||||
<Label className={cx(Classes.LABEL, css.descriptionLabel)} data-tooltip-id={props.dataTooltipId}>
|
||||
{isOptional ? getString('optionalField', { name: getString('tagsLabel') }) : getString('tagsLabel')}
|
||||
{props.dataTooltipId ? <HarnessDocTooltip useStandAlone={true} tooltipId={props.dataTooltipId} /> : null}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const DEFAULT_DATE_TIME_FORMAT = `${DEFAULT_DATE_FORMAT} ${DEFAULT_TIME_F
|
|||
|
||||
export const REPO_KEY_REGEX = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/
|
||||
export const URL_REGEX =
|
||||
/((https?):\/\/)?(www.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#?\/?[a-zA-Z0-9#]+)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/
|
||||
/((https):\/\/)(www.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#?\/?[a-zA-Z0-9#]+)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/
|
||||
|
||||
export enum PreferenceScope {
|
||||
USER = 'USER',
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
import type { IconName } from '@harnessio/icons'
|
||||
import type { UpstreamRepositoryURLInputSource } from '@ar/pages/upstream-proxy-details/types'
|
||||
import type { FormikFowardRef, RepositoryPackageType, RepositoryConfigType, PageType, Scanners } from '@ar/common/types'
|
||||
|
||||
export interface CreateRepositoryFormProps {
|
||||
|
@ -56,6 +57,7 @@ export abstract class RepositoryStep<T, U = unknown> {
|
|||
protected repositoryIconColor?: string
|
||||
protected repositoryIconSize?: number
|
||||
protected supportedScanners?: Scanners[]
|
||||
protected supportedUpstreamURLSources?: UpstreamRepositoryURLInputSource[]
|
||||
|
||||
getPackageType(): string {
|
||||
return this.packageType
|
||||
|
@ -65,6 +67,10 @@ export abstract class RepositoryStep<T, U = unknown> {
|
|||
return this.supportedScanners ?? []
|
||||
}
|
||||
|
||||
getSupportedUpstreamURLSources(): UpstreamRepositoryURLInputSource[] {
|
||||
return this.supportedUpstreamURLSources ?? []
|
||||
}
|
||||
|
||||
getDefaultValues(initialValues: T): T {
|
||||
return { ...this.defaultValues, ...initialValues }
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import UpstreamProxyActions from '@ar/pages/upstream-proxy-details/components/Up
|
|||
import UpstreamProxyConfigurationForm from '@ar/pages/upstream-proxy-details/components/Forms/UpstreamProxyConfigurationForm'
|
||||
import UpstreamProxyCreateFormContent from '@ar/pages/upstream-proxy-details/components/FormContent/UpstreamProxyCreateFormContent'
|
||||
import {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
UpstreamRegistryRequest
|
||||
} from '@ar/pages/upstream-proxy-details/types'
|
||||
|
@ -51,6 +51,11 @@ export class DockerRepositoryType extends RepositoryStep<VirtualRegistryRequest>
|
|||
protected repositoryIcon: IconName = 'docker-step'
|
||||
protected supportedScanners = [Scanners.AQUA_TRIVY]
|
||||
protected supportsUpstreamProxy = true
|
||||
protected supportedUpstreamURLSources = [
|
||||
UpstreamRepositoryURLInputSource.Dockerhub,
|
||||
UpstreamRepositoryURLInputSource.AwsEcr,
|
||||
UpstreamRepositoryURLInputSource.Custom
|
||||
]
|
||||
|
||||
protected defaultValues: VirtualRegistryRequest = {
|
||||
packageType: RepositoryPackageType.DOCKER,
|
||||
|
@ -68,7 +73,7 @@ export class DockerRepositoryType extends RepositoryStep<VirtualRegistryRequest>
|
|||
identifier: '',
|
||||
config: {
|
||||
type: RepositoryConfigType.UPSTREAM,
|
||||
source: DockerRepositoryURLInputSource.Dockerhub,
|
||||
source: UpstreamRepositoryURLInputSource.Dockerhub,
|
||||
url: '',
|
||||
authType: UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
},
|
||||
|
|
|
@ -32,7 +32,7 @@ import UpstreamProxyConfigurationForm from '@ar/pages/upstream-proxy-details/com
|
|||
import UpstreamProxyCreateFormContent from '@ar/pages/upstream-proxy-details/components/FormContent/UpstreamProxyCreateFormContent'
|
||||
import UpstreamProxyActions from '@ar/pages/upstream-proxy-details/components/UpstreamProxyActions/UpstreamProxyActions'
|
||||
import {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
type UpstreamRegistryRequest
|
||||
} from '@ar/pages/upstream-proxy-details/types'
|
||||
|
@ -51,6 +51,10 @@ export class HelmRepositoryType extends RepositoryStep<VirtualRegistryRequest> {
|
|||
protected repositoryIcon: IconName = 'service-helm'
|
||||
protected supportedScanners = []
|
||||
protected supportsUpstreamProxy = true
|
||||
protected supportedUpstreamURLSources = [
|
||||
UpstreamRepositoryURLInputSource.AwsEcr,
|
||||
UpstreamRepositoryURLInputSource.Custom
|
||||
]
|
||||
|
||||
protected defaultValues: VirtualRegistryRequest = {
|
||||
packageType: RepositoryPackageType.HELM,
|
||||
|
@ -67,7 +71,7 @@ export class HelmRepositoryType extends RepositoryStep<VirtualRegistryRequest> {
|
|||
config: {
|
||||
type: RepositoryConfigType.UPSTREAM,
|
||||
authType: UpstreamProxyAuthenticationMode.ANONYMOUS,
|
||||
source: DockerRepositoryURLInputSource.Custom,
|
||||
source: UpstreamRepositoryURLInputSource.Custom,
|
||||
url: ''
|
||||
},
|
||||
cleanupPolicy: [],
|
||||
|
|
|
@ -23,6 +23,6 @@
|
|||
:global(.bp3-form-group .bp3-input),
|
||||
:global(.bp3-form-group .bp3-input-group),
|
||||
:global(.bp3-form-group .bp3-select-popover) {
|
||||
width: 580px;
|
||||
width: var(--input-element-width);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 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 from 'react'
|
||||
import { FormikContextType, connect } from 'formik'
|
||||
import { FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings/String'
|
||||
import { DockerRepositoryURLInputSource, UpstreamRegistryRequest } from '@ar/pages/upstream-proxy-details/types'
|
||||
|
||||
interface DockerRepositoryUrlInputProps {
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
function DockerRepositoryUrlInput(
|
||||
props: DockerRepositoryUrlInputProps & { formik: FormikContextType<UpstreamRegistryRequest> }
|
||||
): JSX.Element {
|
||||
const { readonly, formik } = props
|
||||
const { values } = formik
|
||||
const { getString } = useStrings()
|
||||
const { config } = values
|
||||
const { source } = config
|
||||
return (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormInput.RadioGroup
|
||||
name="config.source"
|
||||
radioGroup={{ inline: true }}
|
||||
disabled={readonly}
|
||||
label={getString('upstreamProxyDetails.createForm.source.title')}
|
||||
items={[
|
||||
{
|
||||
label: getString('upstreamProxyDetails.createForm.source.dockerHub'),
|
||||
value: DockerRepositoryURLInputSource.Dockerhub
|
||||
},
|
||||
{
|
||||
label: getString('upstreamProxyDetails.createForm.source.custom'),
|
||||
value: DockerRepositoryURLInputSource.Custom
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{source === DockerRepositoryURLInputSource.Custom && (
|
||||
<FormInput.Text
|
||||
name="config.url"
|
||||
label={getString('upstreamProxyDetails.createForm.url')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.url')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect<DockerRepositoryUrlInputProps, UpstreamRegistryRequest>(DockerRepositoryUrlInput)
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
.authContainer {
|
||||
padding-left: var(--spacing-xlarge) !important;
|
||||
border-left: 1px solid var(--grey-200);
|
||||
}
|
||||
|
||||
.multiTypeSecretInput {
|
||||
& .labelContainer {
|
||||
width: var(--input-element-width) !important;
|
||||
}
|
||||
& :global(.bp3-popover) {
|
||||
width: 180px !important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const authContainer: string
|
||||
export declare const labelContainer: string
|
||||
export declare const multiTypeSecretInput: string
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2024 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 from 'react'
|
||||
import { get } from 'lodash-es'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { Container, FormInput, Layout, Text } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { useAppStore, useParentComponents } from '@ar/hooks'
|
||||
import MultiTypeSecretInput from '@ar/components/MultiTypeSecretInput/MultiTypeSecretInput'
|
||||
|
||||
import { AuthTypeRadioItems, URLSourceToSupportedAuthTypesMapping } from './constants'
|
||||
import { UpstreamProxyAuthenticationMode, UpstreamRegistryRequest, UpstreamRepositoryURLInputSource } from '../../types'
|
||||
|
||||
import css from './AuthenticationFormInput.module.scss'
|
||||
|
||||
interface AuthenticationFormInputProps {
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
export default function AuthenticationFormInput(props: AuthenticationFormInputProps): JSX.Element {
|
||||
const { readonly } = props
|
||||
const { scope } = useAppStore()
|
||||
const { getString } = useStrings()
|
||||
const { SecretFormInput } = useParentComponents()
|
||||
const formikProps = useFormikContext<UpstreamRegistryRequest>()
|
||||
|
||||
const { values, setFieldValue } = formikProps
|
||||
const { config, packageType } = values
|
||||
const { source } = config
|
||||
const selectedRadioValue = get(values, 'config.authType')
|
||||
const supportedAuthTypes = URLSourceToSupportedAuthTypesMapping[source as UpstreamRepositoryURLInputSource] || []
|
||||
const radioItems = supportedAuthTypes.map(each => AuthTypeRadioItems[each])
|
||||
return (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormInput.RadioGroup
|
||||
key={`${source}-${packageType}`}
|
||||
name="config.authType"
|
||||
onChange={e => {
|
||||
const selectedValue = e.currentTarget.value as UpstreamProxyAuthenticationMode
|
||||
if (selectedValue === UpstreamProxyAuthenticationMode.ANONYMOUS) {
|
||||
setFieldValue('config.auth', null)
|
||||
} else {
|
||||
setFieldValue('config.auth', {})
|
||||
}
|
||||
}}
|
||||
radioGroup={{ inline: true }}
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.title')}
|
||||
items={radioItems.map(each => ({
|
||||
...each,
|
||||
label: (
|
||||
<Layout.Vertical>
|
||||
<Text font={{ variation: FontVariation.SMALL }}>{getString(each.label)}</Text>
|
||||
{each.subLabel && <Text font={{ variation: FontVariation.SMALL }}>{getString(each.subLabel)}</Text>}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}))}
|
||||
disabled={readonly}
|
||||
/>
|
||||
{selectedRadioValue === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD && (
|
||||
<Container className={css.authContainer}>
|
||||
<Layout.Vertical>
|
||||
<FormInput.Text
|
||||
name="config.auth.userName"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.username')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.username')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<SecretFormInput
|
||||
name="config.auth.secretIdentifier"
|
||||
spaceIdFieldName="config.auth.secretSpaceId"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
scope={scope}
|
||||
disabled={readonly}
|
||||
formik={formikProps}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
)}
|
||||
{selectedRadioValue === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY && (
|
||||
<Container className={css.authContainer}>
|
||||
<Layout.Vertical>
|
||||
<MultiTypeSecretInput
|
||||
className={css.multiTypeSecretInput}
|
||||
labelClassName={css.labelContainer}
|
||||
name="config.auth.accessKey"
|
||||
secretField="config.auth.accessKeySecretIdentifier"
|
||||
secretSpaceIdField="config.auth.accessKeySecretSpaceId"
|
||||
typeField="config.auth.accessKeyType"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.accessKey')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.accessKey')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<SecretFormInput
|
||||
name="config.auth.secretKeyIdentifier"
|
||||
spaceIdFieldName="config.auth.secretKeySpaceId"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.secretKey')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.secretKey')}
|
||||
scope={scope}
|
||||
disabled={readonly}
|
||||
formik={formikProps}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2024 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 type { StringsMap } from '@ar/strings/types'
|
||||
import { UpstreamProxyAuthenticationMode, UpstreamRepositoryURLInputSource } from '../../types'
|
||||
|
||||
interface RadioGroupItem {
|
||||
label: keyof StringsMap
|
||||
subLabel?: keyof StringsMap
|
||||
value: UpstreamProxyAuthenticationMode
|
||||
}
|
||||
|
||||
export const AuthTypeRadioItems: Record<UpstreamProxyAuthenticationMode, RadioGroupItem> = {
|
||||
[UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]: {
|
||||
label: 'upstreamProxyDetails.createForm.authentication.userNameAndPassword',
|
||||
value: UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD
|
||||
},
|
||||
[UpstreamProxyAuthenticationMode.ANONYMOUS]: {
|
||||
label: 'upstreamProxyDetails.createForm.authentication.anonymous',
|
||||
subLabel: 'upstreamProxyDetails.createForm.authentication.anonymousSubLabel',
|
||||
value: UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
},
|
||||
[UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY]: {
|
||||
label: 'upstreamProxyDetails.createForm.authentication.accessKeyAndSecretKey',
|
||||
value: UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export const URLSourceToSupportedAuthTypesMapping: Record<
|
||||
UpstreamRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode[]
|
||||
> = {
|
||||
[UpstreamRepositoryURLInputSource.Dockerhub]: [
|
||||
UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
],
|
||||
[UpstreamRepositoryURLInputSource.AwsEcr]: [
|
||||
UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY,
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
],
|
||||
[UpstreamRepositoryURLInputSource.Custom]: [
|
||||
UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
]
|
||||
}
|
|
@ -15,94 +15,22 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { get } from 'lodash-es'
|
||||
import type { FormikProps } from 'formik'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { Container, FormInput, Layout, Text } from '@harnessio/uicore'
|
||||
import { Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { useAppStore, useParentComponents } from '@ar/hooks'
|
||||
import { UpstreamProxyAuthenticationMode, type UpstreamRegistryRequest } from '../../types'
|
||||
import css from './FormContent.module.scss'
|
||||
import RepositoryUrlInput from '../RepositoryUrlInput/RepositoryUrlInput'
|
||||
import AuthenticationFormInput from '../AuthenticationFormInput/AuthenticationFormInput'
|
||||
|
||||
interface UpstreamProxyAuthenticationFormContentProps {
|
||||
formikProps: FormikProps<UpstreamRegistryRequest>
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
export default function UpstreamProxyAuthenticationFormContent({
|
||||
formikProps,
|
||||
readonly
|
||||
}: UpstreamProxyAuthenticationFormContentProps): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const { scope } = useAppStore()
|
||||
const { SecretFormInput } = useParentComponents()
|
||||
const selectedRadioValue = get(formikProps.values, 'config.authType')
|
||||
return (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormInput.RadioGroup
|
||||
name="config.authType"
|
||||
onChange={e => {
|
||||
const selectedValue = e.currentTarget.value as UpstreamProxyAuthenticationMode
|
||||
if (selectedValue === UpstreamProxyAuthenticationMode.ANONYMOUS) {
|
||||
formikProps.setFieldValue('config.auth', null)
|
||||
} else {
|
||||
formikProps.setFieldValue('config.auth', {})
|
||||
}
|
||||
}}
|
||||
radioGroup={{ inline: true }}
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.title')}
|
||||
items={[
|
||||
{
|
||||
label: getString('upstreamProxyDetails.createForm.authentication.userNameAndPassword'),
|
||||
value: UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Layout.Vertical>
|
||||
<Text font={{ variation: FontVariation.SMALL }}>
|
||||
{getString('upstreamProxyDetails.createForm.authentication.anonymous')}
|
||||
</Text>
|
||||
<Text font={{ variation: FontVariation.SMALL }}>
|
||||
{getString('upstreamProxyDetails.createForm.authentication.anonymousSubLabel')}
|
||||
</Text>
|
||||
</Layout.Vertical>
|
||||
),
|
||||
value: UpstreamProxyAuthenticationMode.ANONYMOUS
|
||||
}
|
||||
]}
|
||||
disabled={readonly}
|
||||
/>
|
||||
{selectedRadioValue === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD && (
|
||||
<Container className={css.authContainer}>
|
||||
<Layout.Vertical>
|
||||
<FormInput.Text
|
||||
name="config.auth.userName"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.username')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.username')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
{/* <FormInput.Text
|
||||
name="config.auth.password"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
disabled={readonly}
|
||||
inputGroup={{
|
||||
type: 'password'
|
||||
}}
|
||||
/> */}
|
||||
<SecretFormInput
|
||||
name="config.auth.secretIdentifier"
|
||||
spaceIdFieldName="config.auth.secretSpaceId"
|
||||
label={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.authentication.password')}
|
||||
scope={scope}
|
||||
disabled={readonly}
|
||||
formik={formikProps}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
)}
|
||||
<RepositoryUrlInput readonly={readonly} />
|
||||
<AuthenticationFormInput readonly={readonly} />
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ export default function UpstreamProxyConfigurationFormContent(
|
|||
<Card className={classNames(css.cardContainer, css.marginTopLarge)}>
|
||||
<Layout.Vertical>
|
||||
<UpstreamProxyDetailsFormContent isEdit formikProps={formikProps} readonly={readonly} />
|
||||
<UpstreamProxyAuthenticationFormContent formikProps={formikProps} readonly={readonly} />
|
||||
<UpstreamProxyAuthenticationFormContent readonly={readonly} />
|
||||
</Layout.Vertical>
|
||||
</Card>
|
||||
{parent === Parent.Enterprise && (
|
||||
|
|
|
@ -41,7 +41,7 @@ function UpstreamProxyCreateFormContent(
|
|||
{getString('upstreamProxyDetails.form.title')}
|
||||
</Text>
|
||||
<UpstreamProxyDetailsFormContent isEdit={isEdit} formikProps={formik} readonly={readonly} />
|
||||
<UpstreamProxyAuthenticationFormContent formikProps={formik} readonly={readonly} />
|
||||
<UpstreamProxyAuthenticationFormContent readonly={readonly} />
|
||||
</Layout.Vertical>
|
||||
</FormikForm>
|
||||
)
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import type { FormikProps } from 'formik'
|
||||
import { FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings/String'
|
||||
import RepositoryUrlInput from '../RepositoryUrlInput/RepositoryUrlInput'
|
||||
import { Description, Tags } from '@ar/components/NameDescriptionTags'
|
||||
|
||||
import type { UpstreamRegistryRequest } from '../../types'
|
||||
|
||||
interface UpstreamProxyDetailsFormContentProps {
|
||||
|
@ -31,7 +33,7 @@ interface UpstreamProxyDetailsFormContentProps {
|
|||
export default function UpstreamProxyDetailsFormContent(props: UpstreamProxyDetailsFormContentProps): JSX.Element {
|
||||
const { readonly, isEdit, formikProps } = props
|
||||
const { values } = formikProps
|
||||
const { packageType } = values
|
||||
const { description, labels } = values
|
||||
const { getString } = useStrings()
|
||||
return (
|
||||
<Layout.Vertical>
|
||||
|
@ -44,7 +46,8 @@ export default function UpstreamProxyDetailsFormContent(props: UpstreamProxyDeta
|
|||
autoFocus: true
|
||||
}}
|
||||
/>
|
||||
<RepositoryUrlInput packageType={packageType} readonly={readonly} />
|
||||
<Description hasValue={!!description} disabled={readonly} />
|
||||
<Tags name="labels" hasValue={!isEmpty(labels)} disabled={readonly} />
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
:global(.bp3-form-group .bp3-input),
|
||||
:global(.bp3-form-group .bp3-input-group),
|
||||
:global(.bp3-form-group .bp3-select-popover) {
|
||||
width: 580px;
|
||||
width: var(--input-element-width);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
import React, { forwardRef, useContext } from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import { Formik, FormikForm, getErrorInfoFromErrorObject, useToaster } from '@harnessio/uicore'
|
||||
import { Anonymous, UserPassword, useModifyRegistryMutation } from '@harnessio/react-har-service-client'
|
||||
import { useModifyRegistryMutation } from '@harnessio/react-har-service-client'
|
||||
|
||||
import { URL_REGEX } from '@ar/constants'
|
||||
import { useAppStore, useGetSpaceRef } from '@ar/hooks'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { queryClient } from '@ar/utils/queryClient'
|
||||
|
@ -35,13 +34,12 @@ import {
|
|||
} from '@ar/components/CleanupPolicyList/utils'
|
||||
|
||||
import UpstreamProxyConfigurationFormContent from '../FormContent/UpstreamProxyConfigurationFormContent'
|
||||
import type { UpstreamRegistry, UpstreamRegistryRequest } from '../../types'
|
||||
import {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
type UpstreamRegistry,
|
||||
type UpstreamRegistryRequest
|
||||
} from '../../types'
|
||||
import { getFormattedFormDataForAuthType, getFormattedInitialValuesForAuthType } from './utils'
|
||||
getFormattedFormDataForAuthType,
|
||||
getFormattedInitialValuesForAuthType,
|
||||
getValidationSchemaForUpstreamForm
|
||||
} from './utils'
|
||||
|
||||
import css from './Forms.module.scss'
|
||||
|
||||
|
@ -115,45 +113,7 @@ function UpstreamProxyConfigurationForm(
|
|||
}}
|
||||
formName="upstream-repository-form"
|
||||
initialValues={getInitialValues(data as UpstreamRegistry)}
|
||||
validationSchema={Yup.object().shape({
|
||||
config: Yup.object().shape({
|
||||
authType: Yup.string()
|
||||
.required()
|
||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||
auth: Yup.object()
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||
schema.shape({
|
||||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||
}),
|
||||
otherwise: Yup.object().optional().nullable()
|
||||
})
|
||||
.nullable(),
|
||||
url: Yup.string().when(['source'], {
|
||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||
then: (schema: Yup.StringSchema) =>
|
||||
schema
|
||||
.trim()
|
||||
.required(getString('validationMessages.urlRequired'))
|
||||
.matches(URL_REGEX, getString('validationMessages.urlPattern')),
|
||||
otherwise: (schema: Yup.StringSchema) => schema.trim().notRequired()
|
||||
})
|
||||
}),
|
||||
cleanupPolicy: Yup.array()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
name: Yup.string().trim().required(getString('validationMessages.cleanupPolicy.nameRequired')),
|
||||
expireDays: Yup.number()
|
||||
.required(getString('validationMessages.cleanupPolicy.expireDaysRequired'))
|
||||
.positive(getString('validationMessages.cleanupPolicy.positiveExpireDays'))
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.nullable()
|
||||
})}>
|
||||
validationSchema={Yup.object().shape(getValidationSchemaForUpstreamForm(getString))}>
|
||||
{formik => {
|
||||
setFormikRef(formikRef, formik)
|
||||
return (
|
||||
|
|
|
@ -27,18 +27,16 @@ import {
|
|||
getErrorInfoFromErrorObject,
|
||||
useToaster
|
||||
} from '@harnessio/uicore'
|
||||
import { Anonymous, UserPassword, useCreateRegistryMutation } from '@harnessio/react-har-service-client'
|
||||
import { useCreateRegistryMutation } from '@harnessio/react-har-service-client'
|
||||
|
||||
import { useAppStore, useGetSpaceRef } from '@ar/hooks'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { decodeRef } from '@ar/hooks/useGetSpaceRef'
|
||||
import { setFormikRef } from '@ar/common/utils'
|
||||
import { REPO_KEY_REGEX, URL_REGEX } from '@ar/constants'
|
||||
import { REPO_KEY_REGEX } from '@ar/constants'
|
||||
import { Separator } from '@ar/components/Separator/Separator'
|
||||
import { RepositoryConfigType, type FormikFowardRef } from '@ar/common/types'
|
||||
import {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
UpstreamProxyPackageType,
|
||||
UpstreamRegistry,
|
||||
UpstreamRegistryRequest
|
||||
|
@ -50,7 +48,7 @@ import repositoryFactory from '@ar/frameworks/RepositoryStep/RepositoryFactory'
|
|||
import { getFormattedFormDataForCleanupPolicy } from '@ar/components/CleanupPolicyList/utils'
|
||||
import type { RepositoryAbstractFactory } from '@ar/frameworks/RepositoryStep/RepositoryAbstractFactory'
|
||||
|
||||
import { getFormattedFormDataForAuthType } from './utils'
|
||||
import { getFormattedFormDataForAuthType, getValidationSchemaForUpstreamForm } from './utils'
|
||||
import css from './Forms.module.scss'
|
||||
|
||||
interface FormContentProps {
|
||||
|
@ -190,31 +188,7 @@ function UpstreamProxyCreateForm(props: UpstreamProxyCreateFormProps, formikRef:
|
|||
identifier: Yup.string()
|
||||
.required(getString('validationMessages.nameRequired'))
|
||||
.matches(REPO_KEY_REGEX, getString('validationMessages.repokeyRegExMessage')),
|
||||
config: Yup.object().shape({
|
||||
authType: Yup.string()
|
||||
.required()
|
||||
.oneOf([UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD]),
|
||||
auth: Yup.object()
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||
schema.shape({
|
||||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||
})
|
||||
})
|
||||
.nullable(),
|
||||
url: Yup.string().when(['source'], {
|
||||
is: (source: DockerRepositoryURLInputSource) => source === DockerRepositoryURLInputSource.Custom,
|
||||
then: (schema: Yup.StringSchema) =>
|
||||
schema
|
||||
.trim()
|
||||
.required(getString('validationMessages.urlRequired'))
|
||||
.matches(URL_REGEX, getString('validationMessages.urlPattern')),
|
||||
otherwise: (schema: Yup.StringSchema) => schema.trim().notRequired()
|
||||
})
|
||||
})
|
||||
...getValidationSchemaForUpstreamForm(getString)
|
||||
})}>
|
||||
{(formik: FormikProps<UpstreamRegistryRequest>) => {
|
||||
setFormikRef(formikRef, formik)
|
||||
|
|
|
@ -15,14 +15,18 @@
|
|||
*/
|
||||
|
||||
import produce from 'immer'
|
||||
import { compact, defaultTo, get, set } from 'lodash-es'
|
||||
import type { UserPassword } from '@harnessio/react-har-service-client'
|
||||
import * as Yup from 'yup'
|
||||
import { compact, get, isEmpty, set } from 'lodash-es'
|
||||
import type { AccessKeySecretKey, Anonymous, UserPassword } from '@harnessio/react-har-service-client'
|
||||
|
||||
import { Parent } from '@ar/common/types'
|
||||
import { URL_REGEX } from '@ar/constants'
|
||||
import type { Scope } from '@ar/MFEAppTypes'
|
||||
import type { StringKeys } from '@ar/frameworks/strings'
|
||||
import { SecretValueType } from '@ar/components/MultiTypeSecretInput/MultiTypeSecretInput'
|
||||
|
||||
import {
|
||||
DockerRepositoryURLInputSource,
|
||||
UpstreamRepositoryURLInputSource,
|
||||
UpstreamProxyAuthenticationMode,
|
||||
type UpstreamRegistryRequest
|
||||
} from '../../types'
|
||||
|
@ -46,6 +50,41 @@ export function getReferenceStringFromSecretSpacePath(identifier: string, secret
|
|||
return identifier
|
||||
}
|
||||
|
||||
function convertSecretInputToFormFields(
|
||||
formData: UpstreamRegistryRequest,
|
||||
secretField: string,
|
||||
secretSpacePathField: string,
|
||||
scope?: Scope
|
||||
) {
|
||||
const password = get(formData, secretField)
|
||||
set(formData, secretSpacePathField, getSecretSpacePath(get(password, 'referenceString', ''), scope))
|
||||
set(formData, secretField, get(password, 'identifier'))
|
||||
}
|
||||
|
||||
function convertMultiTypeSecretInputToFormFields(
|
||||
formData: UpstreamRegistryRequest,
|
||||
typeField: string,
|
||||
textField: string,
|
||||
secretField: string,
|
||||
secretSpacePathField: string,
|
||||
scope?: Scope
|
||||
) {
|
||||
const accessKeyType = get(formData, typeField)
|
||||
if (accessKeyType === SecretValueType.TEXT) {
|
||||
const value = get(formData, textField, '')
|
||||
set(formData, textField, value)
|
||||
set(formData, typeField, undefined)
|
||||
set(formData, secretField, undefined)
|
||||
set(formData, secretSpacePathField, undefined)
|
||||
} else {
|
||||
const accessKeySecret = get(formData, secretField)
|
||||
set(formData, secretSpacePathField, getSecretSpacePath(get(accessKeySecret, 'referenceString', ''), scope))
|
||||
set(formData, secretField, get(accessKeySecret, 'identifier'))
|
||||
set(formData, typeField, undefined)
|
||||
set(formData, textField, undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export function getFormattedFormDataForAuthType(
|
||||
values: UpstreamRegistryRequest,
|
||||
parent?: Parent,
|
||||
|
@ -55,13 +94,30 @@ export function getFormattedFormDataForAuthType(
|
|||
if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) {
|
||||
set(draft, 'config.auth.authType', draft.config.authType)
|
||||
if (parent === Parent.Enterprise) {
|
||||
const auth = draft.config.auth as UserPassword
|
||||
const password = auth?.secretIdentifier
|
||||
set(draft, 'config.auth.secretSpacePath', getSecretSpacePath(get(password, 'referenceString', ''), scope))
|
||||
set(draft, 'config.auth.secretIdentifier', get(password, 'identifier'))
|
||||
convertSecretInputToFormFields(draft, 'config.auth.secretIdentifier', 'config.auth.secretSpacePath', scope)
|
||||
}
|
||||
} else if (draft.config.authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY) {
|
||||
set(draft, 'config.auth.authType', draft.config.authType)
|
||||
if (parent === Parent.Enterprise) {
|
||||
convertSecretInputToFormFields(
|
||||
draft,
|
||||
'config.auth.secretKeyIdentifier',
|
||||
'config.auth.secretKeySpacePath',
|
||||
scope
|
||||
)
|
||||
convertMultiTypeSecretInputToFormFields(
|
||||
draft,
|
||||
'config.auth.accessKeyType',
|
||||
'config.auth.accessKey',
|
||||
'config.auth.accessKeySecretIdentifier',
|
||||
'config.auth.accessKeySecretSpacePath',
|
||||
scope
|
||||
)
|
||||
}
|
||||
} else if (draft.config.authType === UpstreamProxyAuthenticationMode.ANONYMOUS) {
|
||||
set(draft, 'config.auth', null)
|
||||
}
|
||||
if (draft.config.source !== DockerRepositoryURLInputSource.Custom) {
|
||||
if (draft.config.source === UpstreamRepositoryURLInputSource.Dockerhub) {
|
||||
set(draft, 'config.url', '')
|
||||
}
|
||||
})
|
||||
|
@ -79,15 +135,137 @@ function getSecretScopeDetailsByIdentifier(identifier: string, secretSpacePath:
|
|||
}
|
||||
}
|
||||
|
||||
function convertFormFieldsToSecreteInput(
|
||||
formData: UpstreamRegistryRequest,
|
||||
secretField: string,
|
||||
secretSpacePathField: string
|
||||
) {
|
||||
const secretIdentifier = get(formData, secretField, '')
|
||||
const secretSpacePath = get(formData, secretSpacePathField, '')
|
||||
set(formData, secretField, getSecretScopeDetailsByIdentifier(secretIdentifier, secretSpacePath))
|
||||
}
|
||||
|
||||
function convertFormFieldsToMultiTypeSecretInput(
|
||||
formData: UpstreamRegistryRequest,
|
||||
typeField: string,
|
||||
formField: string,
|
||||
secretIdentifierField: string,
|
||||
secretSpacePathField: string
|
||||
) {
|
||||
const value = get(formData, formField)
|
||||
if (value) {
|
||||
set(formData, formField, value)
|
||||
set(formData, typeField, SecretValueType.TEXT)
|
||||
set(formData, secretIdentifierField, undefined)
|
||||
set(formData, secretSpacePathField, undefined)
|
||||
} else {
|
||||
const secretIdentifierValue = get(formData, secretIdentifierField, '')
|
||||
const secretSpacePathValue = get(formData, secretSpacePathField, '')
|
||||
set(formData, typeField, SecretValueType.ENCRYPTED)
|
||||
set(formData, secretIdentifierField, getSecretScopeDetailsByIdentifier(secretIdentifierValue, secretSpacePathValue))
|
||||
set(formData, formField, undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export function getFormattedInitialValuesForAuthType(values: UpstreamRegistryRequest, parent?: Parent) {
|
||||
return produce(values, (draft: UpstreamRegistryRequest) => {
|
||||
if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) {
|
||||
if (parent === Parent.Enterprise) {
|
||||
const auth = draft.config.auth as UserPassword
|
||||
const secretIdentifier = defaultTo(auth?.secretIdentifier, '')
|
||||
const secretSpacePath = defaultTo(auth?.secretSpacePath, '')
|
||||
set(draft, 'config.auth.secretIdentifier', getSecretScopeDetailsByIdentifier(secretIdentifier, secretSpacePath))
|
||||
convertFormFieldsToSecreteInput(draft, 'config.auth.secretIdentifier', 'config.auth.secretSpacePath')
|
||||
}
|
||||
}
|
||||
if (draft.config.authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY) {
|
||||
if (parent === Parent.Enterprise) {
|
||||
convertFormFieldsToSecreteInput(draft, 'config.auth.secretKeyIdentifier', 'config.auth.secretKeySpacePath')
|
||||
convertFormFieldsToMultiTypeSecretInput(
|
||||
draft,
|
||||
'config.auth.accessKeyType',
|
||||
'config.auth.accessKey',
|
||||
'config.auth.accessKeySecretIdentifier',
|
||||
'config.auth.accessKeySecretSpacePath'
|
||||
)
|
||||
} else {
|
||||
const accessKeyType = isEmpty(get(draft, 'config.auth.accessKey'))
|
||||
? SecretValueType.ENCRYPTED
|
||||
: SecretValueType.TEXT
|
||||
set(draft, 'config.auth.accessKeyType', accessKeyType)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getValidationSchemaForUpstreamForm(getString: (key: StringKeys, vars?: Record<string, any>) => string) {
|
||||
return {
|
||||
config: Yup.object().shape({
|
||||
authType: Yup.string()
|
||||
.required()
|
||||
.oneOf([
|
||||
UpstreamProxyAuthenticationMode.ANONYMOUS,
|
||||
UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY
|
||||
]),
|
||||
auth: Yup.object()
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD,
|
||||
then: (schema: Yup.ObjectSchema<UserPassword | Anonymous>) =>
|
||||
schema.shape({
|
||||
userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')),
|
||||
secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired'))
|
||||
}),
|
||||
otherwise: Yup.object().optional().nullable()
|
||||
})
|
||||
.when(['authType'], {
|
||||
is: (authType: UpstreamProxyAuthenticationMode) =>
|
||||
authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY,
|
||||
then: (schema: Yup.ObjectSchema<AccessKeySecretKey | Anonymous>) =>
|
||||
schema.shape({
|
||||
accessKey: Yup.string()
|
||||
.trim()
|
||||
.test('access-key-validation', getString('validationMessages.accessKeyRequired'), function (value) {
|
||||
if (this.parent.accessKeyType === SecretValueType.TEXT) {
|
||||
return !isEmpty(value)
|
||||
}
|
||||
return true
|
||||
}),
|
||||
accessKeySecretIdentifier: Yup.string()
|
||||
.trim()
|
||||
.test(
|
||||
'access-key-secret-validation',
|
||||
getString('validationMessages.accessKeyRequired'),
|
||||
function (value) {
|
||||
if (this.parent.accessKeyType === SecretValueType.ENCRYPTED) {
|
||||
return !isEmpty(value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
),
|
||||
secretKeyIdentifier: Yup.string().trim().required(getString('validationMessages.secretKeyRequired'))
|
||||
}),
|
||||
otherwise: Yup.object().optional().nullable()
|
||||
})
|
||||
.nullable(),
|
||||
url: Yup.string().when(['source'], {
|
||||
is: (source: UpstreamRepositoryURLInputSource) =>
|
||||
[UpstreamRepositoryURLInputSource.Custom, UpstreamRepositoryURLInputSource.AwsEcr].includes(source),
|
||||
then: (schema: Yup.StringSchema) =>
|
||||
schema
|
||||
.trim()
|
||||
.required(getString('validationMessages.urlRequired'))
|
||||
.matches(URL_REGEX, getString('validationMessages.urlPattern')),
|
||||
otherwise: (schema: Yup.StringSchema) => schema.trim().notRequired()
|
||||
})
|
||||
}),
|
||||
cleanupPolicy: Yup.array()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
name: Yup.string().trim().required(getString('validationMessages.cleanupPolicy.nameRequired')),
|
||||
expireDays: Yup.number()
|
||||
.required(getString('validationMessages.cleanupPolicy.expireDaysRequired'))
|
||||
.positive(getString('validationMessages.cleanupPolicy.positiveExpireDays'))
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.nullable()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,30 +15,57 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { FormInput } from '@harnessio/uicore'
|
||||
import type { PackageType } from '@harnessio/react-har-service-client'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings/String'
|
||||
import { UpstreamProxyPackageType } from '@ar/pages/upstream-proxy-details/types'
|
||||
import DockerRepositoryUrlInput from '@ar/pages/upstream-proxy-details/DockerRepository/DockerRepositoryUrlInput/DockerRepositoryUrlInput'
|
||||
import repositoryFactory from '@ar/frameworks/RepositoryStep/RepositoryFactory'
|
||||
|
||||
import { UpstreamURLSourceConfig } from './constants'
|
||||
import { UpstreamProxyAuthenticationMode, UpstreamRegistryRequest, UpstreamRepositoryURLInputSource } from '../../types'
|
||||
|
||||
interface RepositoryUrlInputProps {
|
||||
readonly: boolean
|
||||
packageType: PackageType
|
||||
}
|
||||
|
||||
export default function RepositoryUrlInput(props: RepositoryUrlInputProps): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const { readonly, packageType } = props
|
||||
if (packageType === UpstreamProxyPackageType.DOCKER) {
|
||||
return <DockerRepositoryUrlInput readonly={readonly} />
|
||||
}
|
||||
const { readonly } = props
|
||||
const { values, setFieldValue } = useFormikContext<UpstreamRegistryRequest>()
|
||||
const { packageType, config } = values
|
||||
const { source } = config
|
||||
const repositoryType = repositoryFactory.getRepositoryType(packageType)
|
||||
const supportedURLSources = repositoryType?.getSupportedUpstreamURLSources() || []
|
||||
const radioItems = supportedURLSources.map(each => UpstreamURLSourceConfig[each])
|
||||
return (
|
||||
<FormInput.Text
|
||||
name="config.url"
|
||||
label={getString('upstreamProxyDetails.createForm.url')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.url')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<Layout.Vertical spacing="small">
|
||||
{radioItems.length ? (
|
||||
<FormInput.RadioGroup
|
||||
key={packageType}
|
||||
name="config.source"
|
||||
radioGroup={{ inline: true }}
|
||||
disabled={readonly}
|
||||
label={getString('upstreamProxyDetails.createForm.source.title')}
|
||||
items={radioItems.map(each => ({ ...each, label: getString(each.label) }))}
|
||||
onChange={e => {
|
||||
const selectedValue = e.currentTarget.value as UpstreamRepositoryURLInputSource
|
||||
if (source !== selectedValue) {
|
||||
setFieldValue('config.url', '')
|
||||
setFieldValue('config.authType', UpstreamProxyAuthenticationMode.ANONYMOUS)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{[UpstreamRepositoryURLInputSource.Custom, UpstreamRepositoryURLInputSource.AwsEcr].includes(
|
||||
source as UpstreamRepositoryURLInputSource
|
||||
) && (
|
||||
<FormInput.Text
|
||||
name="config.url"
|
||||
label={getString('upstreamProxyDetails.createForm.url')}
|
||||
placeholder={getString('upstreamProxyDetails.createForm.url')}
|
||||
disabled={readonly}
|
||||
/>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 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 type { StringsMap } from '@ar/frameworks/strings'
|
||||
import { UpstreamRepositoryURLInputSource } from '../../types'
|
||||
|
||||
interface RadioGroupItem {
|
||||
label: keyof StringsMap
|
||||
value: UpstreamRepositoryURLInputSource
|
||||
}
|
||||
export const UpstreamURLSourceConfig: Record<UpstreamRepositoryURLInputSource, RadioGroupItem> = {
|
||||
[UpstreamRepositoryURLInputSource.Dockerhub]: {
|
||||
label: 'upstreamProxyDetails.createForm.source.dockerHub',
|
||||
value: UpstreamRepositoryURLInputSource.Dockerhub
|
||||
},
|
||||
[UpstreamRepositoryURLInputSource.AwsEcr]: {
|
||||
label: 'upstreamProxyDetails.createForm.source.ecr',
|
||||
value: UpstreamRepositoryURLInputSource.AwsEcr
|
||||
},
|
||||
[UpstreamRepositoryURLInputSource.Custom]: {
|
||||
label: 'upstreamProxyDetails.createForm.source.custom',
|
||||
value: UpstreamRepositoryURLInputSource.Custom
|
||||
}
|
||||
}
|
|
@ -10,14 +10,18 @@ createForm:
|
|||
source:
|
||||
title: Source
|
||||
dockerHub: Docker Hub
|
||||
ecr: AWS ECR
|
||||
custom: Custom
|
||||
authentication:
|
||||
title: Authentication
|
||||
userNameAndPassword: Username and Password
|
||||
accessKeyAndSecretKey: Access Key and Secret Key
|
||||
anonymous: Anonymous
|
||||
anonymousSubLabel: (No credentials required)
|
||||
username: Username
|
||||
password: Password
|
||||
secretKey: Secret Key
|
||||
accessKey: Access Key
|
||||
create: Create Upstream Proxy
|
||||
editForm:
|
||||
title: Update Upstream Proxy
|
||||
|
|
|
@ -24,13 +24,15 @@ export enum UpstreamProxyPackageType {
|
|||
MAVEN = 'MAVEN'
|
||||
}
|
||||
|
||||
export enum DockerRepositoryURLInputSource {
|
||||
export enum UpstreamRepositoryURLInputSource {
|
||||
Dockerhub = 'Dockerhub',
|
||||
AwsEcr = 'AwsEcr',
|
||||
Custom = 'Custom'
|
||||
}
|
||||
|
||||
export enum UpstreamProxyAuthenticationMode {
|
||||
USER_NAME_AND_PASSWORD = 'UserPassword',
|
||||
ACCESS_KEY_AND_SECRET_KEY = 'AccessKeySecretKey',
|
||||
ANONYMOUS = 'Anonymous'
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ noDescription: No Description
|
|||
lastUpdated: Last Updated
|
||||
createdAt: Created At
|
||||
modifiedAt: Modified At
|
||||
plaintext: Plain Text
|
||||
encrypted: Encrypted
|
||||
tags:
|
||||
latest: LATEST
|
||||
latestVersion: LATEST VERSION
|
||||
|
@ -93,3 +95,5 @@ validationMessages:
|
|||
urlPattern: Remote registry URL must be valid
|
||||
userNameRequired: Username is required
|
||||
passwordRequired: Password is required
|
||||
accessKeyRequired: Access key is required
|
||||
secretKeyRequired: Secret key is required
|
||||
|
|
|
@ -124,9 +124,12 @@ export interface StringsMap {
|
|||
'upstreamProxyDetails.actions.delete.contentText': string
|
||||
'upstreamProxyDetails.actions.delete.repositoryDeleted': string
|
||||
'upstreamProxyDetails.actions.delete.title': string
|
||||
'upstreamProxyDetails.createForm.authentication.accessKey': string
|
||||
'upstreamProxyDetails.createForm.authentication.accessKeyAndSecretKey': string
|
||||
'upstreamProxyDetails.createForm.authentication.anonymous': string
|
||||
'upstreamProxyDetails.createForm.authentication.anonymousSubLabel': string
|
||||
'upstreamProxyDetails.createForm.authentication.password': string
|
||||
'upstreamProxyDetails.createForm.authentication.secretKey': string
|
||||
'upstreamProxyDetails.createForm.authentication.title': string
|
||||
'upstreamProxyDetails.createForm.authentication.userNameAndPassword': string
|
||||
'upstreamProxyDetails.createForm.authentication.username': string
|
||||
|
@ -135,6 +138,7 @@ export interface StringsMap {
|
|||
'upstreamProxyDetails.createForm.packageType': string
|
||||
'upstreamProxyDetails.createForm.source.custom': string
|
||||
'upstreamProxyDetails.createForm.source.dockerHub': string
|
||||
'upstreamProxyDetails.createForm.source.ecr': string
|
||||
'upstreamProxyDetails.createForm.source.title': string
|
||||
'upstreamProxyDetails.createForm.title': string
|
||||
'upstreamProxyDetails.createForm.url': string
|
||||
|
@ -257,6 +261,7 @@ export interface StringsMap {
|
|||
descriptionPlaceholder: string
|
||||
discard: string
|
||||
download: string
|
||||
encrypted: string
|
||||
enterPlaceholder: string
|
||||
failedToLoadData: string
|
||||
harLabel: string
|
||||
|
@ -277,6 +282,7 @@ export interface StringsMap {
|
|||
'packageTypes.dockerPackage': string
|
||||
'packageTypes.genericPackage': string
|
||||
'packageTypes.helmPackage': string
|
||||
plaintext: string
|
||||
plusNewName: string
|
||||
prod: string
|
||||
prodCount: string
|
||||
|
@ -299,12 +305,14 @@ export interface StringsMap {
|
|||
'tags.latestVersion': string
|
||||
tagsLabel: string
|
||||
timeLabel: string
|
||||
'validationMessages.accessKeyRequired': string
|
||||
'validationMessages.cleanupPolicy.expireDaysRequired': string
|
||||
'validationMessages.cleanupPolicy.nameRequired': string
|
||||
'validationMessages.cleanupPolicy.positiveExpireDays': string
|
||||
'validationMessages.nameRequired': string
|
||||
'validationMessages.passwordRequired': string
|
||||
'validationMessages.repokeyRegExMessage': string
|
||||
'validationMessages.secretKeyRequired': string
|
||||
'validationMessages.urlPattern': string
|
||||
'validationMessages.urlRequired': string
|
||||
'validationMessages.userNameRequired': string
|
||||
|
|
|
@ -14,6 +14,20 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.createNewBtn {
|
||||
margin-top: 5px !important;
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.createNewBtn {
|
||||
margin-top: 5px !important;
|
||||
}
|
||||
|
||||
&.containerWithoutLabel {
|
||||
align-items: flex-start !important;
|
||||
|
||||
.createNewBtn {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,6 @@
|
|||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const container: string
|
||||
export declare const containerWithoutLabel: string
|
||||
export declare const createNewBtn: string
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import React from 'react'
|
||||
import { get } from 'lodash-es'
|
||||
import classNames from 'classnames'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ButtonVariation, FormInput, Layout, SelectOption } from '@harnessio/uicore'
|
||||
|
@ -73,7 +74,12 @@ export default function SecretFormInput(props: SecretFormInputProps) {
|
|||
const selectedValue = formikValue ? { label: formikValue, value: formikValue } : null
|
||||
|
||||
return (
|
||||
<Layout.Horizontal spacing="small" flex={{ justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<Layout.Horizontal
|
||||
className={classNames(css.container, {
|
||||
[css.containerWithoutLabel]: !label
|
||||
})}
|
||||
spacing="small"
|
||||
flex={{ justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<FormInput.Select
|
||||
name={name}
|
||||
label={label}
|
||||
|
|
|
@ -1945,10 +1945,10 @@
|
|||
yargs "^17.6.2"
|
||||
zod "^3.19.1"
|
||||
|
||||
"@harnessio/react-har-service-client@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.3.0.tgz#b434034093c6619effe42720c7c078d18a9ecc50"
|
||||
integrity sha512-+pFkxlUUM9CEkB60kx3SjppCdHdycfJhRN6t4p1R0M/n93dQLrdI0uHkoDZEeWb0o1Q0oQ21pkwTYktQr24E3g==
|
||||
"@harnessio/react-har-service-client@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.4.0.tgz#ad0deb70502f8ff51c7199d72dd67aa7a13640b4"
|
||||
integrity sha512-ndVz0Ig0FHHwgDy1oZVQ7I2epFgM0dnYLLAUHOLMzaqvcV6pN8RCuawtygcFi9K2DKMfFQieWTuI9UjVoB8M+w==
|
||||
dependencies:
|
||||
"@harnessio/oats-cli" "^3.0.0"
|
||||
|
||||
|
|
Loading…
Reference in New Issue