feat: [CDE-898]: allowed image paths in admin settings (#4186)

* refactor: restructure devcontainer image configuration with access list and image details

* refactor: simplify image validation pattern and update error message for wildcard usage

* refactor: extract image path cells into separate components and simplify form data merge

* feat: [CDE-898]: allowed image paths in admin settings
This commit is contained in:
Sayantan Mondal 2025-08-01 10:49:43 +00:00 committed by Harness
parent 487b753b2a
commit 669fbd6aa6
18 changed files with 352 additions and 140 deletions

View File

@ -0,0 +1,38 @@
.mainContentCard {
padding: var(--spacing-large);
margin: 0;
border-radius: var(--border-radius-medium);
box-shadow: 0 2px 8px rgba(96, 97, 112, 0.16);
width: 100%;
background: var(--white);
}
.imagePathCard {
width: 100%;
margin: 0;
}
.imagePathCellContainer {
min-height: 60px;
}
.defaultImageTable {
:global {
.bp3-table {
border: none;
}
.bp3-table-cell {
vertical-align: middle;
&:last-child {
border-right: none;
}
}
}
}
.defaultImageTableRow {
padding: 0 !important;
border-bottom: none !important;
}

View File

@ -0,0 +1,23 @@
/*
* 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 defaultImageTable: string
export declare const defaultImageTableRow: string
export declare const imagePathCard: string
export declare const imagePathCellContainer: string
export declare const mainContentCard: string

View File

@ -0,0 +1,129 @@
import React, { useMemo } from 'react'
import { Card, Text, Layout, Button, ButtonVariation, Container, TableV2, FormInput } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { useFormikContext, FieldArray } from 'formik'
import type { Column } from 'react-table'
import { useStrings } from 'framework/strings'
import type { AdminSettingsFormValues } from 'cde-gitness/pages/AdminSettings/utils/adminSettingsUtils'
import css from './AllowedImagePaths.module.scss'
interface ConnectorRowData {
imagePath: string
}
interface CellRenderProps {
row: { index: number }
remove: (index: number) => void
}
type CellRenderFunction = (props: CellRenderProps) => React.ReactNode
type ColumnType = Column<ConnectorRowData> & {
Cell?: CellRenderFunction
}
const ImagePathCell = React.memo(({ row }: Pick<CellRenderProps, 'row'>) => {
return (
<Container flex={{ alignItems: 'flex-start', justifyContent: 'flex-start' }} className={css.imagePathCellContainer}>
<FormInput.Text
name={`gitspaceImages.access_list.list[${row.index}]`}
placeholder="mcr.microsoft.com/devcontainers/java"
className={css.imagePathCard}
/>
</Container>
)
})
ImagePathCell.displayName = 'ImagePathCell'
const DeleteCell = React.memo(({ row, remove }: CellRenderProps) => {
return (
<Container flex={{ alignItems: 'flex-start', justifyContent: 'flex-end' }} className={css.imagePathCellContainer}>
<Button
variation={ButtonVariation.ICON}
icon="main-trash"
iconProps={{ size: 24 }}
onClick={() => remove(row.index)}
/>
</Container>
)
})
DeleteCell.displayName = 'DeleteCell'
export const AllowedImagePaths: React.FC = () => {
const { values } = useFormikContext<AdminSettingsFormValues>()
const { getString } = useStrings()
const paths = values.gitspaceImages?.access_list?.list || []
const showTable = paths.length > 0
const baseColumns = useMemo<ColumnType[]>(
() => [
{
id: 'imagePath',
Header: '',
accessor: 'imagePath',
width: '100%',
Cell: ImagePathCell
},
{
id: 'delete',
Header: '',
width: '10%',
Cell: DeleteCell
}
],
[]
)
return (
<Card className={css.mainContentCard}>
<Layout.Vertical spacing="medium">
<Text font={{ variation: FontVariation.H5 }}>
{getString('cde.settings.images.allowedImagePathsAndRegistries')}
</Text>
<Text font={{ variation: FontVariation.BODY }} color={Color.GREY_500}>
{getString('cde.settings.images.allowedImagePathsDescription')}
</Text>
<FieldArray name="gitspaceImages.access_list.list">
{({ push, remove }) => (
<>
{showTable && (
<Container margin={{ top: 'medium' }}>
<TableV2<ConnectorRowData>
getRowClassName={() => css.defaultImageTableRow}
className={css.defaultImageTable}
columns={baseColumns.map(col => {
if (col.id === 'delete') {
return {
...col,
Cell: ({ row }: Pick<CellRenderProps, 'row'>) => {
return React.createElement(DeleteCell, { row, remove })
}
}
}
return col
})}
data={paths.map(imagePath => ({ imagePath }))}
minimal
hideHeaders
/>
</Container>
)}
<Button
variation={ButtonVariation.SECONDARY}
width="220px"
margin={{ top: 'medium' }}
onClick={() => push('')}>
{getString('cde.settings.images.newImagePath')}
</Button>
</>
)}
</FieldArray>
</Layout.Vertical>
</Card>
)
}

View File

@ -43,5 +43,5 @@
}
.defaultImageTableRow {
padding: var(--spacing-large) var(--spacing-medium) var(--spacing-large) var(--spacing-large) !important;
padding: var(--spacing-large) var(--spacing-medium) var(--spacing-small) var(--spacing-medium) !important;
}

View File

@ -5,7 +5,7 @@ import { Color, FontVariation } from '@harnessio/design-system'
import { useFormikContext } from 'formik'
import { useStrings } from 'framework/strings'
import { ProvideDefaultImageModal } from 'components/ProvideDefaultImage/ProvideDefaultImage'
import type { AdminSettingsFormValues } from 'cde-gitness/utils/cloudRegionsUtils'
import type { AdminSettingsFormValues } from 'cde-gitness/pages/AdminSettings/utils/adminSettingsUtils'
import { useAppContext } from 'AppContext'
import type { TypesGitspaceSettingsResponse } from 'services/cde'
import { getConnectorIcon } from 'cde-gitness/pages/AdminSettings/utils/connectorUtils'
@ -16,11 +16,11 @@ interface DefaultGitspaceImageProps {
}
interface ConnectorRowData {
name?: string
identifier?: string
type?: string
id?: string
imagePath?: string
name: string
identifier: string
type: string
id: string
imagePath: string
}
interface TableRowProps {
@ -61,6 +61,7 @@ export const DefaultGitspaceImage: React.FC<DefaultGitspaceImageProps> = ({ sett
iconProps={{ size: 24 }}
onClick={() => {
setFieldValue('gitspaceImages', {
...values.gitspaceImages,
image_name: undefined,
image_connector_ref: undefined,
default_image_added: false
@ -70,7 +71,7 @@ export const DefaultGitspaceImage: React.FC<DefaultGitspaceImageProps> = ({ sett
/>
</Container>
),
[setFieldValue]
[setFieldValue, values.gitspaceImages]
)
const ImagePathCell = useCallback(
@ -194,7 +195,6 @@ export const DefaultGitspaceImage: React.FC<DefaultGitspaceImageProps> = ({ sett
setIsModalOpen(false)
if (formValues?.imagePath) {
setShowRow(true)
setFieldValue('gitspaceImages.default_image_added', true)
}
}}
/>

View File

@ -4,7 +4,8 @@ import { Color, FontVariation } from '@harnessio/design-system'
import { useFormikContext } from 'formik'
import FlagsIcon from 'cde-gitness/assests/Flags.svg?url'
import { useStrings } from 'framework/strings'
import type { RegionData, AdminSettingsFormValues } from 'cde-gitness/utils/cloudRegionsUtils'
import type { RegionData } from 'cde-gitness/utils/cloudRegionsUtils'
import type { AdminSettingsFormValues } from 'cde-gitness/pages/AdminSettings/utils/adminSettingsUtils'
import MachineTypeCard from '../MachineTypeCard/MachineTypeCard'
import css from './RegionAccordion.module.scss'

View File

@ -3,8 +3,9 @@ import { Text, Layout, Container } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { useFormikContext } from 'formik'
import AWSIcon from 'cde-gitness/assests/aws.svg?url'
import type { InfraProviderResource, RegionData, AdminSettingsFormValues } from 'cde-gitness/utils/cloudRegionsUtils'
import type { InfraProviderResource, RegionData } from 'cde-gitness/utils/cloudRegionsUtils'
import { useStrings } from 'framework/strings'
import type { AdminSettingsFormValues } from 'cde-gitness/pages/AdminSettings/utils/adminSettingsUtils'
import GCPIcon from '../../../icons/google-cloud.svg?url'
import HarnessIcon from '../../../icons/Harness.svg?url'
import RegionAccordion from '../RegionAccordion/RegionAccordion'

View File

@ -16,7 +16,7 @@
.tabContainer {
width: 100% !important;
height: calc(var(--page-height) - var(--page-header-height, 64px));
min-height: calc(var(--page-height) - var(--page-header-height, 64px));
display: flex;
[class*='bp3-tab-list'] {
@ -25,10 +25,6 @@
padding: 0 var(--spacing-xlarge) !important;
}
[class*='bp3-tab'] {
margin-right: 0 !important;
}
:global(.bp3-tabs) {
display: flex;
flex-direction: column;
@ -38,11 +34,10 @@
:global(.bp3-tab-panel) {
flex-grow: 1;
margin-top: 0;
padding: 0 !important; /* The panel should not have its own padding if the content inside handles it */
padding: 0 !important;
}
}
.tabContent {
padding: var(--spacing-large);
min-height: 400px;
}

View File

@ -1,112 +1,99 @@
import React from 'react'
import {
Breadcrumbs,
Container,
Heading,
Layout,
Page,
Tabs,
Container,
Button,
ButtonVariation,
Tabs,
Layout,
Heading,
Breadcrumbs,
PageError,
Formik,
FormikForm
} from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import * as Yup from 'yup'
import { routes } from 'cde-gitness/RouteDefinitions'
import { useAppContext } from 'AppContext'
import { useStrings } from 'framework/strings'
import { useAdminSettings } from './hooks/useAdminSettings'
import { getValidationSchema, AdminSettingsTabs } from './utils/adminSettingsUtils'
import GitProviders from './GitProviders/GitProviders'
import CodeEditors from './CodeEditors/CodeEditors'
import CloudRegions from './CloudRegions/CloudRegions'
import GitspaceImages from './GitspaceImages/GitspaceImages'
import css from './AdminSettings.module.scss'
const AdminSettingsPage = () => {
const AdminSettingsPage: React.FC = () => {
const { getString } = useStrings()
const { accountInfo } = useAppContext()
const { settings, tabs, initialValues, selectedTab, loading, errorSettings, handleSave, handleTabChange, refetch } =
useAdminSettings()
const validationSchema = Yup.object({
gitspaceImages: Yup.object({
image_name: Yup.string().when('default_image_added', {
is: true,
then: (schema: Yup.StringSchema) =>
schema.required(getString('validation.nameIsRequired')).trim().min(1, getString('validation.nameIsRequired')),
otherwise: (schema: Yup.StringSchema) => schema.notRequired()
})
}).notRequired()
})
return (
<Formik
formName="adminSettings"
initialValues={initialValues}
onSubmit={handleSave}
enableReinitialize
validationSchema={validationSchema}>
{() => {
return (
<FormikForm>
<Page.Header
title={
<Layout.Horizontal>
<Heading level={4} font={{ variation: FontVariation.H4 }}>
{getString('cde.gitspaces')}
</Heading>
</Layout.Horizontal>
}
content={
<Layout.Horizontal spacing="medium">
<Button type="submit" variation={ButtonVariation.PRIMARY}>
{getString('save')}
</Button>
</Layout.Horizontal>
}
breadcrumbs={
<Breadcrumbs
className={css.customBreadcumbStyles}
links={[
{
url: routes.toModuleRoute({ accountId: accountInfo?.identifier }),
label: `${getString('cde.account')}: ${accountInfo?.name}`
}
]}
validationSchema={getValidationSchema(getString)}>
{() => (
<FormikForm>
<Page.Header
title={
<Layout.Horizontal>
<Heading level={4} font={{ variation: FontVariation.H4 }}>
{getString('cde.gitspaces')}
</Heading>
</Layout.Horizontal>
}
content={
<Layout.Horizontal spacing="medium">
<Button type="submit" variation={ButtonVariation.PRIMARY}>
{getString('save')}
</Button>
</Layout.Horizontal>
}
breadcrumbs={
<Breadcrumbs
className={css.customBreadcumbStyles}
links={[
{
url: routes.toModuleRoute({ accountId: accountInfo?.identifier }),
label: `${getString('cde.account')}: ${accountInfo?.name}`
}
]}
/>
}
/>
<Page.Body loading={loading}>
{errorSettings ? (
<PageError message={errorSettings.message} onClick={() => refetch()} />
) : (
<Container className={css.tabContainer}>
<Tabs
id="adminSettingsTabs"
selectedTabId={selectedTab}
onChange={handleTabChange}
tabList={tabs.map(tab => ({
id: tab.id,
title: tab.title,
panel: (
<>
{tab.id === AdminSettingsTabs.GIT_PROVIDERS && <GitProviders />}
{tab.id === AdminSettingsTabs.CODE_EDITORS && <CodeEditors />}
{tab.id === AdminSettingsTabs.CLOUD_REGIONS && <CloudRegions settings={settings} />}
{tab.id === AdminSettingsTabs.GITSPACE_IMAGES && <GitspaceImages settings={settings} />}
</>
)
}))}
/>
}
/>
<Page.Body loading={loading}>
{errorSettings ? (
<PageError message={errorSettings.message} onClick={() => refetch()} />
) : (
<Container className={css.tabContainer}>
<Tabs
id={'adminSettingsTabs'}
selectedTabId={selectedTab}
onChange={handleTabChange}
tabList={tabs.map(tab => ({
id: tab.id,
title: tab.title,
panel: (
<>
{tab.id === 'gitProviders' && <GitProviders />}
{tab.id === 'codeEditors' && <CodeEditors />}
{tab.id === 'cloudRegions' && <CloudRegions settings={settings} />}
{tab.id === 'gitspaceImages' && <GitspaceImages settings={settings} />}
</>
)
}))}
/>
</Container>
)}
</Page.Body>
</FormikForm>
)
}}
</Container>
)}
</Page.Body>
</FormikForm>
)}
</Formik>
)
}

View File

@ -5,11 +5,8 @@ import type { TypesGitspaceSettingsResponse } from 'services/cde'
import InfraProviderPanel from 'cde-gitness/components/InfraProviderPanel/InfraProviderPanel'
import RegionsPanel from 'cde-gitness/components/RegionsPanel/RegionsPanel'
import { useInfraProviderResources } from 'cde-gitness/pages/AdminSettings/CloudRegions/hooks/useInfraProviderResources'
import {
AdminSettingsFormValues,
getCloudRegionFieldPath,
processInfraProviderDenyList
} from 'cde-gitness/utils/cloudRegionsUtils'
import { getCloudRegionFieldPath, processInfraProviderDenyList } from 'cde-gitness/utils/cloudRegionsUtils'
import type { AdminSettingsFormValues } from '../utils/adminSettingsUtils'
import css from './CloudRegions.module.scss'
interface CloudRegionsProps {

View File

@ -3,6 +3,7 @@ import { Text, Layout } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { useStrings } from 'framework/strings'
import { DefaultGitspaceImage } from 'cde-gitness/components/DefaultGitspaceImage/DefaultGitspaceImage'
import { AllowedImagePaths } from 'cde-gitness/components/AllowedImagePaths/AllowedImagePaths'
import type { TypesGitspaceSettingsResponse } from 'services/cde'
import css from './GitspaceImages.module.scss'
@ -23,6 +24,7 @@ const GitspaceImages: React.FC<GitspaceImagesProps> = ({ settings }: GitspaceIma
</Layout.Vertical>
<DefaultGitspaceImage settings={settings} />
<AllowedImagePaths />
</div>
)
}

View File

@ -7,14 +7,15 @@ import { useAppContext } from 'AppContext'
import {
createInitialValues,
buildAdminSettingsPayload,
type AdminSettingsFormValues
type AdminSettingsFormValues,
AdminSettingsTabs
} from '../utils/adminSettingsUtils'
export const useAdminSettings = () => {
const { getString } = useStrings()
const { showSuccess, showError } = useToaster()
const { accountInfo } = useAppContext()
const [selectedTab, setSelectedTab] = useState('gitProviders')
const [selectedTab, setSelectedTab] = useState(AdminSettingsTabs.GIT_PROVIDERS)
const {
data: settings,
@ -31,10 +32,10 @@ export const useAdminSettings = () => {
const tabs = useMemo(
() => [
{ id: 'gitProviders', title: getString('cde.settings.gitProviders') },
{ id: 'codeEditors', title: getString('cde.settings.codeEditors') },
{ id: 'cloudRegions', title: getString('cde.settings.cloudRegionsAndMachineTypes') },
{ id: 'gitspaceImages', title: getString('cde.settings.gitspaceImages') }
{ id: AdminSettingsTabs.GIT_PROVIDERS, title: getString('cde.settings.gitProviders') },
{ id: AdminSettingsTabs.CODE_EDITORS, title: getString('cde.settings.codeEditors') },
{ id: AdminSettingsTabs.CLOUD_REGIONS, title: getString('cde.settings.cloudRegionsAndMachineTypes') },
{ id: AdminSettingsTabs.GITSPACE_IMAGES, title: getString('cde.settings.gitspaceImages') }
],
[getString]
)
@ -53,7 +54,7 @@ export const useAdminSettings = () => {
}
}
const handleTabChange = (tabId: string) => {
const handleTabChange = (tabId: AdminSettingsTabs) => {
setSelectedTab(tabId)
}

View File

@ -1,3 +1,4 @@
import * as Yup from 'yup'
import type {
TypesGitspaceSettingsData,
TypesGitspaceSettingsResponse,
@ -10,6 +11,13 @@ import { getIDETypeOptions, type IDEOption } from 'cde-gitness/constants'
import type { EnumInfraProviderType } from 'cde-gitness/services'
import type { StringsMap } from 'framework/strings/stringTypes'
export enum AdminSettingsTabs {
GIT_PROVIDERS = 'gitProviders',
CODE_EDITORS = 'codeEditors',
CLOUD_REGIONS = 'cloudRegions',
GITSPACE_IMAGES = 'gitspaceImages'
}
export interface AdminSettingsFormValues {
gitProviders: {
[key: string]: boolean
@ -137,7 +145,12 @@ export const transformGitspaceImagesToPayload = (formValues: AdminSettingsFormVa
return {
devcontainer_image: {
...formValues.gitspaceImages
access_list: {
mode: 'allow' as const,
list: formValues.gitspaceImages.access_list?.list || []
},
image_name: formValues.gitspaceImages.image_name,
image_connector_ref: formValues.gitspaceImages.image_connector_ref
}
}
}
@ -164,3 +177,27 @@ export const buildAdminSettingsPayload = (
infra_provider: transformCloudRegionsToPayload(formValues, settings)
}
}
const imagePattern = /^[^*]*\*?$/
export const getValidationSchema = (getString: (key: keyof StringsMap) => string) => {
return Yup.object({
gitspaceImages: Yup.object({
access_list: Yup.object({
list: Yup.array()
.of(
Yup.string()
.required(getString('validation.imagePathIsRequired'))
.matches(imagePattern, getString('validation.invalidImage'))
)
.notRequired()
}).notRequired(),
image_name: Yup.string().when('default_image_added', {
is: true,
then: (schema: Yup.StringSchema) =>
schema.required(getString('validation.pathIsRequired')).trim().min(1, getString('validation.pathIsRequired')),
otherwise: (schema: Yup.StringSchema) => schema.notRequired()
})
}).notRequired()
})
}

View File

@ -102,7 +102,8 @@ settings:
privateRegistryDescription: Use private registry with authenticated access
selectImageRegistryConnector: Select Image Registry Connector
selectConnector: -Select Connector-
imageRegistryOrPath: Image Registry or path
imageRegistryOrPath: Image Registry or Path
imagePath: Image Path
apply: Apply
cancel: Cancel
allowedImagePathsAndRegistries: Allowed Image Paths and Registries
@ -330,17 +331,17 @@ create:
infraProviderFailed: Gitspace Infrastructure creation failed
machineCreateSuccess: Machine created successfully
machineCreateFailed: Machine creation failed
gitprovider : Git Provider
scmEmpty : All SCM options are disabled
ideEmpty : All IDEs disabled
selectGitProvider : Select a Git Provider
githubOauthhelpertext1 : Please Configure the GitHub OAuth to connect to the repositories you have access
githubOauthhelpertext2 : Configure the OAuth in Profile
githubOauthhelpertext3 : Visit the User Profile Settings.
githubOauthhelpertext4 : Under OAuth section, select Github and connect
githubOauthhelpertext5 : After configuring return back to this page to connect the private repositories
ideNote : Your Gitspace will open in the selected IDE to code
selectIde : Select an IDE
gitprovider: Git Provider
scmEmpty: All SCM options are disabled
ideEmpty: All IDEs disabled
selectGitProvider: Select a Git Provider
githubOauthhelpertext1: Please Configure the GitHub OAuth to connect to the repositories you have access
githubOauthhelpertext2: Configure the OAuth in Profile
githubOauthhelpertext3: Visit the User Profile Settings.
githubOauthhelpertext4: Under OAuth section, select Github and connect
githubOauthhelpertext5: After configuring return back to this page to connect the private repositories
ideNote: Your Gitspace will open in the selected IDE to code
selectIde: Select an IDE
subtext: Start Coding, no setup required. Dedicated cloud development environments with your favorite editor.
importWarning:

View File

@ -1,4 +1,4 @@
import type { TypesDevcontainerImage, TypesGitspaceSettingsResponse } from 'services/cde'
import type { TypesGitspaceSettingsResponse } from 'services/cde'
export interface MachineType {
identifier: string
@ -24,17 +24,6 @@ export interface InfraProviderResource {
regions: RegionData[]
}
export interface AdminSettingsFormValues {
cloudRegions: {
[infraProvider: string]: {
[region: string]: {
[machineTypeId: string]: boolean
}
}
}
gitspaceImages?: TypesDevcontainerImage
}
export const getCloudRegionFieldPath = (infraProvider: string, region: string, machineTypeId: string): string => {
return `cloudRegions.${infraProvider}.${region}.${machineTypeId}`
}

View File

@ -4,7 +4,7 @@ import { Button, ButtonVariation, Dialog, FormInput, Layout, Text, FormikForm, F
import { Color, FontVariation } from '@harnessio/design-system'
import { useFormikContext } from 'formik'
import { useStrings } from 'framework/strings'
import type { AdminSettingsFormValues } from 'cde-gitness/utils/cloudRegionsUtils'
import type { AdminSettingsFormValues } from 'cde-gitness/pages/AdminSettings/utils/adminSettingsUtils'
import { useAppContext } from 'AppContext'
import {
PublicPrivateRegistrySelect,
@ -26,12 +26,12 @@ interface ProvideDefaultImageModalProps {
export const ProvideDefaultImageModal: React.FC<ProvideDefaultImageModalProps> = ({ isOpen, onClose }) => {
const { getString } = useStrings()
const { setFieldValue } = useFormikContext<AdminSettingsFormValues>()
const { values, setFieldValue } = useFormikContext<AdminSettingsFormValues>()
const { customComponents, accountInfo } = useAppContext()
const { MultiTypeConnectorField } = customComponents
const validationSchema = Yup.object({
imagePath: Yup.string().required(getString('validation.nameIsRequired')),
imagePath: Yup.string().required(getString('validation.pathIsRequired')),
connectorRef: Yup.mixed().when('accessType', {
is: 'private',
then: () => Yup.mixed().required(getString('validation.connectorRequired')),
@ -40,16 +40,20 @@ export const ProvideDefaultImageModal: React.FC<ProvideDefaultImageModalProps> =
})
const handleApply = useCallback(
(values: ModalFormValues) => {
(modalValues: ModalFormValues) => {
const currentGitspaceImages = values.gitspaceImages || {}
const defaultImageData = {
image_name: values.imagePath,
image_connector_ref: values.connectorRef
...currentGitspaceImages,
image_name: modalValues.imagePath,
image_connector_ref: modalValues.connectorRef,
default_image_added: true
}
setFieldValue('gitspaceImages', defaultImageData)
onClose(values)
onClose(modalValues)
},
[setFieldValue, onClose]
[setFieldValue, onClose, values.gitspaceImages]
)
if (!isOpen) {
@ -108,7 +112,7 @@ export const ProvideDefaultImageModal: React.FC<ProvideDefaultImageModalProps> =
<Layout.Vertical spacing="small" margin={{ bottom: 'medium' }}>
<Text font={{ variation: FontVariation.BODY2_SEMI }} color={Color.GREY_700}>
{getString('cde.settings.images.imageRegistryOrPath')}
{getString('cde.settings.images.imagePath')}
</Text>
<FormInput.Text name="imagePath" placeholder="e.g mcr.microsoft.com/devcontainers/java" />
</Layout.Vertical>

View File

@ -1229,6 +1229,8 @@ export interface StringsMap {
'validation.expirationDateRequired': string
'validation.gitBranchNameInvalid': string
'validation.gitTagNameInvalid': string
'validation.imagePathIsRequired': string
'validation.invalidImage': string
'validation.key': string
'validation.nameInvalid': string
'validation.nameIsRequired': string
@ -1236,6 +1238,7 @@ export interface StringsMap {
'validation.nameTooLong': string
'validation.nameTooShort': string
'validation.newPasswordRequired': string
'validation.pathIsRequired': string
'validation.repoNamePatternIsNotValid': string
'validation.spaceNamePatternIsNotValid': string
'validation.uidInvalid': string
@ -1657,6 +1660,7 @@ export interface StringsMap {
'cde.settings.images.defaultImageDescription': string
'cde.settings.images.defaultPathToPrivateGitspaceImagePath': string
'cde.settings.images.defaultPathToPublicGitspaceImagePath': string
'cde.settings.images.imagePath': string
'cde.settings.images.imagePathPlaceholder': string
'cde.settings.images.imageRegistryOrPath': string
'cde.settings.images.manageGitspaceImages': string

View File

@ -146,6 +146,9 @@ validation:
emailRequired: Email is required
uidRequired: User ID is required
nameIsRequired: Name is required
pathIsRequired: Path is required
imagePathIsRequired: Image path is required
invalidImage: "Invalid image expression: the wildcard character '*' can appear at most once and only at the end."
connectorRequired: Connector is required
expirationDateRequired: Expiration date is required
newPasswordRequired: New password is a required field