Skip to content

Commit 7e52d75

Browse files
author
Bogdan Tsechoev
committed
fix: white screen crash for custom Docker tags
1 parent 597d1d3 commit 7e52d75

File tree

3 files changed

+182
-58
lines changed

3 files changed

+182
-58
lines changed

ui/packages/shared/pages/Instance/Configuration/index.tsx

Lines changed: 89 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*--------------------------------------------------------------------------
66
*/
77

8-
import { useState, useEffect } from 'react'
8+
import { useState, useEffect, useMemo } from 'react'
99
import { observer } from 'mobx-react-lite'
1010
import Editor from '@monaco-editor/react'
1111
import {
@@ -35,6 +35,9 @@ import {
3535
uniqueChipValue,
3636
customOrGenericImage,
3737
genericDockerImages,
38+
getImageMajorVersion,
39+
createFallbackDockerImage,
40+
createEnhancedDockerImages,
3841
} from './utils'
3942
import {
4043
SelectWithTooltip,
@@ -194,6 +197,32 @@ export const Configuration = observer(
194197
const [{ formik, connectionData, isConnectionDataValid }] =
195198
useForm(onSubmit)
196199

200+
// Memoized enhanced Docker images to avoid recreation on every render
201+
// This combines predefined images with any custom image from configuration
202+
const enhancedDockerImages = useMemo(() => {
203+
return createEnhancedDockerImages(
204+
configData?.dockerImageType === 'Generic Postgres' ? configData?.dockerPath : undefined,
205+
configData?.dockerImageType === 'Generic Postgres' ? configData?.dockerTag : undefined
206+
)
207+
}, [configData?.dockerPath, configData?.dockerTag, configData?.dockerImageType])
208+
209+
// Memoized computed values from enhanced images
210+
const dockerImageVersions = useMemo(() => {
211+
return enhancedDockerImages
212+
.map((image) => image.pg_major_version)
213+
.filter((value, index, self) => self.indexOf(value) === index)
214+
.sort((a, b) => Number(a) - Number(b))
215+
}, [enhancedDockerImages])
216+
217+
// Memoized tags and locations for performance
218+
const dockerTags = useMemo(() => {
219+
return enhancedDockerImages.map((image) => image.tag)
220+
}, [enhancedDockerImages])
221+
222+
const dockerLocations = useMemo(() => {
223+
return enhancedDockerImages.map((image) => image.location)
224+
}, [enhancedDockerImages])
225+
197226
const scrollToField = () => {
198227
const errorElement = document.querySelector('.Mui-error')
199228
if (errorElement) {
@@ -457,30 +486,23 @@ export const Configuration = observer(
457486
e: React.ChangeEvent<HTMLInputElement>,
458487
) => {
459488
if (e.target.value === 'Generic Postgres') {
460-
const genericImageVersions = genericDockerImages
461-
.map((image) => image.pg_major_version)
462-
.filter((value, index, self) => self.indexOf(value) === index)
463-
.sort((a, b) => Number(a) - Number(b))
464-
const currentDockerImage = genericImageVersions.slice(-1)[0]
489+
// Use memoized enhanced list for better performance
490+
const currentDockerImage = dockerImageVersions.slice(-1)[0]
465491

466492
setDockerState({
467493
...dockerState,
468-
tags: genericDockerImages
469-
.map((image) => image.tag)
470-
.filter((tag) => tag.startsWith(currentDockerImage)),
471-
locations: genericDockerImages
472-
.map((image) => image.location)
473-
.filter((location) => location?.includes(currentDockerImage)),
474-
images: genericImageVersions,
475-
data: genericDockerImages,
494+
tags: dockerTags.filter((tag) => tag.startsWith(currentDockerImage)),
495+
locations: dockerLocations.filter((location) => location?.includes(currentDockerImage)),
496+
images: dockerImageVersions,
497+
data: enhancedDockerImages,
476498
})
477499

478500
formik.setValues({
479501
...formik.values,
480502
dockerImage: currentDockerImage,
481503
dockerImageType: e.target.value,
482-
dockerTag: genericDockerImages.map((image) => image.tag)[0],
483-
dockerPath: genericDockerImages.map((image) => image.location)[0],
504+
dockerTag: dockerTags[0],
505+
dockerPath: dockerLocations[0],
484506
sharedPreloadLibraries:
485507
'pg_stat_statements,pg_stat_kcache,pg_cron,pgaudit,anon',
486508
})
@@ -520,16 +542,28 @@ export const Configuration = observer(
520542
tags: updatedDockerTags,
521543
})
522544

523-
const currentLocation = dockerState.data.find(
524-
(image) => image.tag === updatedDockerTags[0],
525-
)?.location as string
545+
// Add safety check for empty array
546+
const firstTag = updatedDockerTags[0]
547+
if (firstTag) {
548+
const currentLocation = dockerState.data.find(
549+
(image) => image.tag === firstTag,
550+
)?.location
526551

527-
formik.setValues({
528-
...formik.values,
529-
dockerTag: updatedDockerTags[0],
530-
dockerImage: e.target.value,
531-
dockerPath: currentLocation,
532-
})
552+
formik.setValues({
553+
...formik.values,
554+
dockerTag: firstTag,
555+
dockerImage: e.target.value,
556+
dockerPath: currentLocation || '',
557+
})
558+
} else {
559+
// Fallback when no matching tags found
560+
formik.setValues({
561+
...formik.values,
562+
dockerImage: e.target.value,
563+
dockerTag: '',
564+
dockerPath: '',
565+
})
566+
}
533567
} else {
534568
formik.setValues({
535569
...formik.values,
@@ -553,41 +587,46 @@ export const Configuration = observer(
553587

554588
if (customOrGenericImage(configData?.dockerImageType)) {
555589
if (configData?.dockerImageType === 'Generic Postgres') {
556-
const genericImageVersions = genericDockerImages
557-
.map((image) => image.pg_major_version)
558-
.filter((value, index, self) => self.indexOf(value) === index)
559-
.sort((a, b) => Number(a) - Number(b))
560-
const currentDockerImage =
561-
genericDockerImages.filter(
562-
(image) => image.location === configData?.dockerPath,
563-
)[0] ||
564-
genericDockerImages.filter((image) =>
565-
configData?.dockerPath?.includes(image.pg_major_version),
566-
)[0]
590+
// Use memoized enhanced list for better performance
591+
const currentDockerImage = enhancedDockerImages.find(
592+
(image) => image.location === configData?.dockerPath || image.tag === configData?.dockerTag
593+
)
567594

568-
setDockerState({
569-
...dockerState,
570-
tags: genericDockerImages
571-
.map((image) => image.tag)
572-
.filter((tag) =>
595+
if (currentDockerImage) {
596+
setDockerState({
597+
...dockerState,
598+
tags: dockerTags.filter((tag) =>
573599
tag.startsWith(currentDockerImage.pg_major_version),
574600
),
575-
images: genericImageVersions,
576-
data: genericDockerImages,
577-
})
601+
images: dockerImageVersions,
602+
data: enhancedDockerImages,
603+
})
578604

579-
formik.setFieldValue('dockerTag', currentDockerImage?.tag)
580-
formik.setFieldValue(
581-
'dockerImage',
582-
currentDockerImage.pg_major_version,
583-
)
605+
formik.setFieldValue('dockerTag', currentDockerImage.tag)
606+
formik.setFieldValue('dockerImage', currentDockerImage.pg_major_version)
607+
formik.setFieldValue('dockerPath', currentDockerImage.location)
608+
} else {
609+
// Fallback: shouldn't happen with enhancedDockerImages, but keep for safety
610+
const fallbackVersion = dockerImageVersions.slice(-1)[0]
611+
612+
setDockerState({
613+
...dockerState,
614+
tags: dockerTags.filter((tag) => tag.startsWith(fallbackVersion)),
615+
images: dockerImageVersions,
616+
data: enhancedDockerImages,
617+
})
618+
619+
formik.setFieldValue('dockerTag', configData?.dockerTag || '')
620+
formik.setFieldValue('dockerImage', fallbackVersion)
621+
formik.setFieldValue('dockerPath', configData?.dockerPath || '')
622+
}
584623
} else {
585624
formik.setFieldValue('dockerImage', configData?.dockerPath)
586625
}
587626
}
588627
}
589628
}
590-
}, [config])
629+
}, [config, configData?.dockerPath, configData?.dockerTag, configData?.dockerImageType])
591630

592631
useEffect(() => {
593632
getEngine(instanceId).then((res) => {

ui/packages/shared/pages/Instance/Configuration/utils/index.ts

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { FormValues } from '../useForm'
55

66
const seContainerRegistry = 'se-images'
77
const genericImagePrefix = 'postgresai/extended-postgres'
8-
// since some tags are rc, we need to specify the exact tags to use
8+
// Predefined list of Docker images for UI display
9+
// This list is shown to users for convenient selection
10+
// IMPORTANT: if user specified an image in config that's not in this list,
11+
// it will be automatically added via createEnhancedDockerImages()
912
const dockerImagesConfig = {
1013
'9.6': ['0.5.3', '0.5.2', '0.5.1'],
1114
'10': ['0.5.3', '0.5.2', '0.5.1'],
@@ -110,11 +113,22 @@ export const getImageType = (imageUrl: string) => {
110113
}
111114

112115
export const getImageMajorVersion = (pgImage: string | undefined) => {
113-
const pgImageVersion = pgImage?.split(':')[1]
114-
const pgServerVersion = pgImageVersion?.split('-')[0]
115-
return pgServerVersion?.includes('.')
116-
? pgServerVersion?.split('.')[0]
117-
: pgServerVersion
116+
if (!pgImage) return undefined
117+
118+
try {
119+
const pgImageVersion = pgImage.split(':')[1]
120+
if (!pgImageVersion) return undefined
121+
122+
const pgServerVersion = pgImageVersion.split('-')[0]
123+
if (!pgServerVersion) return undefined
124+
125+
return pgServerVersion.includes('.')
126+
? pgServerVersion.split('.')[0]
127+
: pgServerVersion
128+
} catch (error) {
129+
// Return undefined for malformed image strings
130+
return undefined
131+
}
118132
}
119133

120134
export const formatDatabases = (databases: DatabaseType | null) => {
@@ -151,3 +165,71 @@ export const postUniqueCustomOptions = (options: string) => {
151165

152166
export const customOrGenericImage = (dockerImage: string | undefined) =>
153167
dockerImage === 'Generic Postgres' || dockerImage === 'custom'
168+
169+
export const createFallbackDockerImage = (
170+
dockerPath: string,
171+
dockerTag: string,
172+
): DockerImage => {
173+
const majorVersion = getImageMajorVersion(dockerPath) || '17' // Default to 17 if version can't be extracted
174+
175+
return {
176+
package_group: 'postgresai',
177+
pg_major_version: majorVersion,
178+
tag: dockerTag || `${majorVersion}-custom`,
179+
location: dockerPath,
180+
}
181+
}
182+
183+
// Creates enhanced list of Docker images, including image from configuration
184+
export const createEnhancedDockerImages = (
185+
configDockerPath?: string,
186+
configDockerTag?: string,
187+
): DockerImage[] => {
188+
let enhancedImages = [...genericDockerImages]
189+
190+
// If there's an image in config, check if we need to add it
191+
if (configDockerPath && configDockerTag) {
192+
const existingImage = genericDockerImages.find(
193+
(image) => image.location === configDockerPath || image.tag === configDockerTag
194+
)
195+
196+
// If image not found in predefined list, add it
197+
if (!existingImage) {
198+
// Check if this is a Generic Postgres image
199+
if (configDockerPath.includes(genericImagePrefix)) {
200+
// For Generic Postgres images create proper structure
201+
const majorVersion = getImageMajorVersion(configDockerPath)
202+
if (majorVersion) {
203+
const configImage: DockerImage = {
204+
package_group: 'postgresai',
205+
pg_major_version: majorVersion,
206+
tag: configDockerTag,
207+
location: configDockerPath,
208+
}
209+
enhancedImages.push(configImage)
210+
} else {
211+
// Fallback if version extraction failed
212+
const configImage = createFallbackDockerImage(configDockerPath, configDockerTag)
213+
enhancedImages.push(configImage)
214+
}
215+
} else {
216+
// For custom images use fallback
217+
const configImage = createFallbackDockerImage(configDockerPath, configDockerTag)
218+
enhancedImages.push(configImage)
219+
}
220+
}
221+
}
222+
223+
return enhancedImages
224+
}
225+
226+
// Checks if image is loaded from configuration (not from predefined list)
227+
export const isConfigLoadedImage = (
228+
dockerPath: string,
229+
dockerTag: string,
230+
): boolean => {
231+
const existingImage = genericDockerImages.find(
232+
(image) => image.location === dockerPath || image.tag === dockerTag
233+
)
234+
return !existingImage
235+
}

ui/packages/shared/types/api/entities/config.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,15 @@ export const formatConfig = (config: configTypes) => {
6363
debug: config.global?.debug,
6464
dockerImage: isSeDockerImage(dockerImage)
6565
? getImageMajorVersion(dockerImage)
66+
: dockerImage && getImageType(dockerImage) === 'Generic Postgres'
67+
? getImageMajorVersion(dockerImage) || dockerImage
6668
: dockerImage,
6769
...(dockerImage && {
6870
dockerImageType: getImageType(dockerImage),
6971
}),
70-
...(isSeDockerImage(dockerImage) && {
71-
dockerTag: dockerImage?.split(':')[1],
72+
// Extract dockerTag for both SE images and Generic Postgres images
73+
...(dockerImage && dockerImage.includes(':') && {
74+
dockerTag: dockerImage.split(':')[1],
7275
}),
7376
dockerPath: dockerImage,
7477
tuningParams: formatTuningParams(config.databaseConfigs?.configs),

0 commit comments

Comments
 (0)