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:
parent
487b753b2a
commit
669fbd6aa6
|
|
@ -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;
|
||||
}
|
||||
23
web/src/cde-gitness/components/AllowedImagePaths/AllowedImagePaths.module.scss.d.ts
vendored
Normal file
23
web/src/cde-gitness/components/AllowedImagePaths/AllowedImagePaths.module.scss.d.ts
vendored
Normal 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
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue