@postgres.ai/shared 3.5.1-pr-1027.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/.gitlab-ci.yml +60 -0
  2. package/components/AlertSnackbar/index.tsx +23 -0
  3. package/components/AlertSnackbar/useAlertSnackbar.tsx +65 -0
  4. package/components/Button/index.tsx +79 -0
  5. package/components/Button2/index.tsx +43 -0
  6. package/components/Button2/styles.module.scss +82 -0
  7. package/components/DestroyCloneModal/index.tsx +56 -0
  8. package/components/DestroyCloneRestrictionModal/index.tsx +50 -0
  9. package/components/ErrorStub/index.tsx +83 -0
  10. package/components/FormattedText/index.tsx +44 -0
  11. package/components/FormattedText/styles.module.scss +34 -0
  12. package/components/GatewayLink/index.tsx +33 -0
  13. package/components/HorizontalScrollContainer/index.tsx +131 -0
  14. package/components/HorizontalScrollContainer/types.ts +12 -0
  15. package/components/HorizontalScrollContainer/utils.ts +16 -0
  16. package/components/ImportantText/index.tsx +29 -0
  17. package/components/Link2/index.tsx +31 -0
  18. package/components/Link2/styles.module.scss +12 -0
  19. package/components/MenuButton/index.tsx +80 -0
  20. package/components/MenuButton/styles.module.scss +42 -0
  21. package/components/Modal/index.tsx +93 -0
  22. package/components/PageSpinner/index.tsx +18 -0
  23. package/components/PageSpinner/styles.module.scss +13 -0
  24. package/components/ResetCloneModal/index.tsx +154 -0
  25. package/components/SectionTitle/index.tsx +74 -0
  26. package/components/Select/index.tsx +42 -0
  27. package/components/SimpleModalControls/index.tsx +56 -0
  28. package/components/Spinner/icon.tsx +29 -0
  29. package/components/Spinner/index.tsx +16 -0
  30. package/components/Spinner/styles.module.scss +33 -0
  31. package/components/Status/index.tsx +61 -0
  32. package/components/Status/styles.module.scss +45 -0
  33. package/components/StubContainer/index.tsx +41 -0
  34. package/components/StubSpinner/index.tsx +49 -0
  35. package/components/StubSpinnerFlex/index.tsx +20 -0
  36. package/components/StubSpinnerFlex/styles.module.scss +20 -0
  37. package/components/SyntaxHighlight/index.tsx +107 -0
  38. package/components/Table/RowMenu/index.tsx +111 -0
  39. package/components/Table/index.tsx +140 -0
  40. package/components/Text/index.tsx +28 -0
  41. package/components/TextField/index.tsx +117 -0
  42. package/components/Tooltip/index.tsx +52 -0
  43. package/config/index.ts +32 -0
  44. package/config/links.ts +6 -0
  45. package/craco.config.js +80 -0
  46. package/helpers/getEntropy.ts +232 -0
  47. package/helpers/localStorage.ts +15 -0
  48. package/helpers/request.ts +47 -0
  49. package/hooks/useWindowDimensions.ts +16 -0
  50. package/icons/ArrowDropDown/index.tsx +29 -0
  51. package/icons/Circle/index.tsx +27 -0
  52. package/icons/External/index.tsx +14 -0
  53. package/icons/Info/index.tsx +12 -0
  54. package/icons/Renewable/index.tsx +65 -0
  55. package/icons/Shield/index.tsx +33 -0
  56. package/icons/Warning/index.tsx +29 -0
  57. package/meta.json +1 -0
  58. package/package.json +55 -0
  59. package/pages/Clone/Status/index.tsx +73 -0
  60. package/pages/Clone/context.ts +22 -0
  61. package/pages/Clone/index.tsx +634 -0
  62. package/pages/Clone/stores/Main.ts +206 -0
  63. package/pages/Clone/useCreatedStores.ts +11 -0
  64. package/pages/Configuration/Header/index.tsx +84 -0
  65. package/pages/Configuration/InputWithTooltip/index.tsx +240 -0
  66. package/pages/Configuration/ResponseMessage/index.tsx +71 -0
  67. package/pages/Configuration/configOptions.ts +60 -0
  68. package/pages/Configuration/index.tsx +1184 -0
  69. package/pages/Configuration/styles.module.scss +122 -0
  70. package/pages/Configuration/tooltipText.tsx +157 -0
  71. package/pages/Configuration/useForm.ts +108 -0
  72. package/pages/Configuration/utils/index.ts +153 -0
  73. package/pages/CreateClone/index.tsx +311 -0
  74. package/pages/CreateClone/stores/Main.ts +107 -0
  75. package/pages/CreateClone/styles.module.scss +71 -0
  76. package/pages/CreateClone/useCreatedStores.ts +11 -0
  77. package/pages/CreateClone/useForm.ts +36 -0
  78. package/pages/Instance/Clones/Header/Item/index.tsx +15 -0
  79. package/pages/Instance/Clones/Header/Item/styles.module.scss +17 -0
  80. package/pages/Instance/Clones/Header/index.tsx +74 -0
  81. package/pages/Instance/Clones/Header/styles.module.scss +11 -0
  82. package/pages/Instance/Clones/index.tsx +135 -0
  83. package/pages/Instance/ClonesModal/index.tsx +71 -0
  84. package/pages/Instance/ClonesModal/utils.ts +21 -0
  85. package/pages/Instance/InactiveInstance/index.tsx +165 -0
  86. package/pages/Instance/InactiveInstance/utils.ts +9 -0
  87. package/pages/Instance/Info/Connection/ConnectModal/Content/index.tsx +176 -0
  88. package/pages/Instance/Info/Connection/ConnectModal/Content/utils.ts +24 -0
  89. package/pages/Instance/Info/Connection/ConnectModal/index.tsx +36 -0
  90. package/pages/Instance/Info/Connection/index.tsx +81 -0
  91. package/pages/Instance/Info/Details/index.tsx +20 -0
  92. package/pages/Instance/Info/Disks/Disk/ActionsMenu/index.tsx +100 -0
  93. package/pages/Instance/Info/Disks/Disk/Marker/index.tsx +26 -0
  94. package/pages/Instance/Info/Disks/Disk/ProgressBar/PointerIcon.tsx +20 -0
  95. package/pages/Instance/Info/Disks/Disk/ProgressBar/index.tsx +73 -0
  96. package/pages/Instance/Info/Disks/Disk/Status/index.tsx +75 -0
  97. package/pages/Instance/Info/Disks/Disk/index.tsx +168 -0
  98. package/pages/Instance/Info/Disks/index.tsx +65 -0
  99. package/pages/Instance/Info/Icons/index.tsx +39 -0
  100. package/pages/Instance/Info/Retrieval/RefreshFailedAlert/index.tsx +32 -0
  101. package/pages/Instance/Info/Retrieval/RefreshFailedAlert/styles.module.scss +33 -0
  102. package/pages/Instance/Info/Retrieval/RetrievalModal/index.tsx +49 -0
  103. package/pages/Instance/Info/Retrieval/RetrievalModal/styles.module.scss +6 -0
  104. package/pages/Instance/Info/Retrieval/RetrievalTable/index.tsx +53 -0
  105. package/pages/Instance/Info/Retrieval/RetrievalTable/styles.module.scss +29 -0
  106. package/pages/Instance/Info/Retrieval/index.tsx +95 -0
  107. package/pages/Instance/Info/Retrieval/utils.ts +10 -0
  108. package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +125 -0
  109. package/pages/Instance/Info/Snapshots/Calendar/index.tsx +133 -0
  110. package/pages/Instance/Info/Snapshots/Calendar/utils.ts +74 -0
  111. package/pages/Instance/Info/Snapshots/TimeLine/Day/index.tsx +79 -0
  112. package/pages/Instance/Info/Snapshots/TimeLine/index.tsx +57 -0
  113. package/pages/Instance/Info/Snapshots/index.tsx +97 -0
  114. package/pages/Instance/Info/Snapshots/utils.ts +18 -0
  115. package/pages/Instance/Info/Status/InstanceResponseModal/index.tsx +32 -0
  116. package/pages/Instance/Info/Status/InstanceResponseModal/styles.module.scss +3 -0
  117. package/pages/Instance/Info/Status/index.tsx +85 -0
  118. package/pages/Instance/Info/Status/styles.module.scss +12 -0
  119. package/pages/Instance/Info/Status/utils.ts +24 -0
  120. package/pages/Instance/Info/components/Property/index.tsx +32 -0
  121. package/pages/Instance/Info/components/Property/styles.module.scss +21 -0
  122. package/pages/Instance/Info/components/Section/index.tsx +50 -0
  123. package/pages/Instance/Info/components/ValueStatus/index.tsx +51 -0
  124. package/pages/Instance/Info/index.tsx +129 -0
  125. package/pages/Instance/SnapshotsModal/index.tsx +169 -0
  126. package/pages/Instance/SnapshotsModal/utils.ts +17 -0
  127. package/pages/Instance/Tabs/index.tsx +98 -0
  128. package/pages/Instance/components/ClonesList/ConnectionModal/index.tsx +196 -0
  129. package/pages/Instance/components/ClonesList/MenuCell/index.tsx +98 -0
  130. package/pages/Instance/components/ClonesList/MenuCell/utils.ts +21 -0
  131. package/pages/Instance/components/ClonesList/index.tsx +189 -0
  132. package/pages/Instance/components/ClonesList/styles.module.scss +32 -0
  133. package/pages/Instance/components/ErrorStub/index.tsx +77 -0
  134. package/pages/Instance/components/ModalReloadButton/index.tsx +43 -0
  135. package/pages/Instance/components/Tags/Tag/index.tsx +60 -0
  136. package/pages/Instance/components/Tags/index.tsx +42 -0
  137. package/pages/Instance/context.ts +39 -0
  138. package/pages/Instance/index.tsx +235 -0
  139. package/pages/Instance/stores/ClonesModal.ts +35 -0
  140. package/pages/Instance/stores/Main.ts +335 -0
  141. package/pages/Instance/stores/SnapshotsModal.ts +35 -0
  142. package/pages/Instance/styles.scss +40 -0
  143. package/pages/Instance/useCreatedStores.ts +14 -0
  144. package/pages/Logs/Icons/PlusIcon.tsx +8 -0
  145. package/pages/Logs/constants/index.ts +7 -0
  146. package/pages/Logs/hooks/useWsScroll.tsx +44 -0
  147. package/pages/Logs/index.tsx +267 -0
  148. package/pages/Logs/utils/index.ts +20 -0
  149. package/pages/Logs/wsLogs.ts +110 -0
  150. package/pages/Logs/wsSnackbar.ts +27 -0
  151. package/postgres.ai-shared-3.5.0.tgz +0 -0
  152. package/react-app-env.d.ts +71 -0
  153. package/scripts/copy-assets.js +30 -0
  154. package/scripts/pack.js +70 -0
  155. package/stores/Snapshots.ts +54 -0
  156. package/styles/colors.ts +67 -0
  157. package/styles/global.scss +29 -0
  158. package/styles/icons.tsx +1917 -0
  159. package/styles/mixins.scss +30 -0
  160. package/styles/styles.ts +87 -0
  161. package/styles/theme.ts +53 -0
  162. package/styles/vars.scss +43 -0
  163. package/styles/vars.ts +40 -0
  164. package/tsconfig.build.json +37 -0
  165. package/tsconfig.json +30 -0
  166. package/types/api/endpoints/createClone.ts +10 -0
  167. package/types/api/endpoints/destroyClone.ts +7 -0
  168. package/types/api/endpoints/getClone.ts +6 -0
  169. package/types/api/endpoints/getConfig.ts +6 -0
  170. package/types/api/endpoints/getEngine.ts +13 -0
  171. package/types/api/endpoints/getFullConfig.ts +4 -0
  172. package/types/api/endpoints/getInstance.ts +6 -0
  173. package/types/api/endpoints/getInstanceRetrieval.ts +6 -0
  174. package/types/api/endpoints/getSeImages.ts +22 -0
  175. package/types/api/endpoints/getSnapshots.ts +6 -0
  176. package/types/api/endpoints/getWSToken.ts +6 -0
  177. package/types/api/endpoints/initWS.ts +1 -0
  178. package/types/api/endpoints/refreshInstance.ts +4 -0
  179. package/types/api/endpoints/resetClone.ts +8 -0
  180. package/types/api/endpoints/testDbSource.ts +48 -0
  181. package/types/api/endpoints/updateClone.ts +10 -0
  182. package/types/api/endpoints/updateConfig.ts +6 -0
  183. package/types/api/entities/clone.ts +42 -0
  184. package/types/api/entities/config.ts +114 -0
  185. package/types/api/entities/dbSource.ts +13 -0
  186. package/types/api/entities/instance.ts +67 -0
  187. package/types/api/entities/instanceRetrieval.ts +46 -0
  188. package/types/api/entities/instanceState.ts +102 -0
  189. package/types/api/entities/pool.ts +27 -0
  190. package/types/api/entities/snapshot.ts +18 -0
  191. package/types/api/entities/wsToken.ts +7 -0
  192. package/types/byte-size/index.d.ts +22 -0
  193. package/utils/api.ts +30 -0
  194. package/utils/clone.ts +31 -0
  195. package/utils/connection.ts +38 -0
  196. package/utils/date.ts +87 -0
  197. package/utils/instance.ts +10 -0
  198. package/utils/numbers.ts +11 -0
  199. package/utils/react.ts +10 -0
  200. package/utils/snapshot.ts +4 -0
  201. package/utils/strings.ts +11 -0
  202. package/utils/units.ts +23 -0
@@ -0,0 +1,1184 @@
1
+ /*--------------------------------------------------------------------------
2
+ * Copyright (c) 2019-2021, Postgres.ai, Nikolay Samokhvalov nik@postgres.ai
3
+ * All Rights Reserved. Proprietary and confidential.
4
+ * Unauthorized copying of this file, via any small is strictly prohibited
5
+ *--------------------------------------------------------------------------
6
+ */
7
+
8
+ import { useState, useEffect } from 'react'
9
+ import { observer } from 'mobx-react-lite'
10
+ import Editor from '@monaco-editor/react'
11
+ import {
12
+ Checkbox,
13
+ FormControlLabel,
14
+ Typography,
15
+ Snackbar,
16
+ makeStyles,
17
+ Button,
18
+ } from '@material-ui/core'
19
+ import Box from '@mui/material/Box'
20
+
21
+ import { Modal } from '@postgres.ai/shared/components/Modal'
22
+ import { StubSpinner } from '@postgres.ai/shared/components/StubSpinner'
23
+ import { ExternalIcon } from '@postgres.ai/shared/icons/External'
24
+ import { Spinner } from '@postgres.ai/shared/components/Spinner'
25
+ import { useStores } from '@postgres.ai/shared/pages/Instance/context'
26
+ import { MainStore } from '@postgres.ai/shared/pages/Instance/stores/Main'
27
+
28
+ import { tooltipText } from './tooltipText'
29
+ import { FormValues, useForm } from './useForm'
30
+ import { ResponseMessage } from './ResponseMessage'
31
+ import { ConfigSectionTitle, Header, ModalTitle } from './Header'
32
+ import { dockerImageOptions, imagePgOptions } from './configOptions'
33
+ import {
34
+ FormValuesKey,
35
+ uniqueChipValue,
36
+ customOrGenericImage,
37
+ genericDockerImages,
38
+ } from './utils'
39
+ import {
40
+ SelectWithTooltip,
41
+ InputWithChip,
42
+ InputWithTooltip,
43
+ } from './InputWithTooltip'
44
+
45
+ import styles from './styles.module.scss'
46
+ import { SeImages } from '@postgres.ai/shared/types/api/endpoints/getSeImages'
47
+ import {
48
+ formatTuningParams,
49
+ formatTuningParamsToObj,
50
+ } from '@postgres.ai/shared/types/api/endpoints/testDbSource'
51
+
52
+ type PgOptionsType = {
53
+ optionType: string
54
+ pgDumpOptions: string[]
55
+ pgRestoreOptions: string[]
56
+ }
57
+
58
+ const NON_LOGICAL_RETRIEVAL_MESSAGE =
59
+ 'Configuration editing is only available in logical mode'
60
+ const PREVENT_MODIFYING_MESSAGE = 'Editing is disabled by admin'
61
+
62
+ const useStyles = makeStyles(
63
+ {
64
+ checkboxRoot: {
65
+ padding: '9px 10px',
66
+ },
67
+ grayText: {
68
+ color: '#8a8a8a',
69
+ fontSize: '12px',
70
+ },
71
+ },
72
+ { index: 1 },
73
+ )
74
+
75
+ export const Configuration = observer(
76
+ ({
77
+ switchActiveTab,
78
+ reload,
79
+ isConfigurationActive,
80
+ disableConfigModification,
81
+ }: {
82
+ switchActiveTab: (_: null, activeTab: number) => void
83
+ reload: () => void
84
+ isConfigurationActive: boolean
85
+ disableConfigModification?: boolean
86
+ }) => {
87
+ const classes = useStyles()
88
+ const stores = useStores()
89
+ const {
90
+ config,
91
+ isConfigurationLoading,
92
+ updateConfig,
93
+ getSeImages,
94
+ fullConfig,
95
+ testDbSource,
96
+ configError,
97
+ getFullConfig,
98
+ getFullConfigError,
99
+ getEngine,
100
+ } = stores.main
101
+
102
+ const configData: MainStore['config'] =
103
+ config && JSON.parse(JSON.stringify(config))
104
+ const isConfigurationDisabled =
105
+ !isConfigurationActive || disableConfigModification
106
+
107
+ const [dleEdition, setDledition] = useState('')
108
+ const isCeEdition = dleEdition === 'community'
109
+ const filteredDockerImageOptions = isCeEdition
110
+ ? dockerImageOptions.filter(
111
+ (option) =>
112
+ option.type === 'custom' || option.type === 'Generic Postgres',
113
+ )
114
+ : dockerImageOptions
115
+
116
+ const [isModalOpen, setIsModalOpen] = useState(false)
117
+ const [submitState, setSubmitState] = useState({
118
+ status: '',
119
+ response: '' as string | React.ReactNode,
120
+ })
121
+ const [dockerState, setDockerState] = useState({
122
+ loading: false,
123
+ error: '',
124
+ tags: [] as string[],
125
+ locations: [] as string[],
126
+ images: [] as string[],
127
+ preloadLibraries: '' as string | undefined,
128
+ data: [] as SeImages[],
129
+ })
130
+ const [testConnectionState, setTestConnectionState] = useState({
131
+ default: {
132
+ loading: false,
133
+ error: '',
134
+ message: {
135
+ status: '',
136
+ message: '',
137
+ },
138
+ },
139
+ dockerImage: {
140
+ loading: false,
141
+ error: '',
142
+ message: {
143
+ status: '',
144
+ message: '',
145
+ },
146
+ },
147
+ fetchTuning: {
148
+ loading: false,
149
+ error: '',
150
+ message: {
151
+ status: '',
152
+ message: '',
153
+ },
154
+ },
155
+ })
156
+
157
+ const switchTab = async () => {
158
+ reload()
159
+ switchActiveTab(null, 0)
160
+ }
161
+
162
+ const onSubmit = async (values: FormValues) => {
163
+ setSubmitState({
164
+ ...submitState,
165
+ response: '',
166
+ })
167
+ await updateConfig({
168
+ ...values,
169
+ tuningParams: formatTuningParamsToObj(
170
+ values.tuningParams,
171
+ ) as unknown as string,
172
+ }).then((response) => {
173
+ if (response?.ok) {
174
+ setSubmitState({
175
+ status: 'success',
176
+ response: (
177
+ <p>
178
+ Changes applied.{' '}
179
+ <span className={styles.underline} onClick={switchTab}>
180
+ Switch to Overview
181
+ </span>{' '}
182
+ to see details and to work with clones
183
+ </p>
184
+ ),
185
+ })
186
+ }
187
+ })
188
+ }
189
+ const [{ formik, connectionData, isConnectionDataValid }] =
190
+ useForm(onSubmit)
191
+
192
+ const scrollToField = () => {
193
+ const errorElement = document.querySelector('.Mui-error')
194
+ if (errorElement) {
195
+ errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
196
+ const inputElement = errorElement.querySelector('input')
197
+ if (inputElement) {
198
+ setTimeout(() => {
199
+ inputElement.focus()
200
+ }, 1000)
201
+ }
202
+ }
203
+ }
204
+
205
+ const onTestConnectionClick = async ({
206
+ type,
207
+ }: {
208
+ type: 'default' | 'dockerImage' | 'fetchTuning'
209
+ }) => {
210
+ Object.keys(connectionData).map(function (key: string) {
211
+ if (key !== 'password' && key !== 'db_list') {
212
+ formik.validateField(key).then(() => {
213
+ scrollToField()
214
+ })
215
+ }
216
+ })
217
+ if (isConnectionDataValid) {
218
+ setTestConnectionState({
219
+ ...testConnectionState,
220
+ [type]: {
221
+ ...testConnectionState[type as keyof typeof testConnectionState],
222
+ loading: true,
223
+ error: '',
224
+ message: {
225
+ status: '',
226
+ message: '',
227
+ },
228
+ },
229
+ })
230
+ testDbSource(connectionData)
231
+ .then((res) => {
232
+ if (res?.response) {
233
+ setTestConnectionState({
234
+ ...testConnectionState,
235
+ [type]: {
236
+ ...testConnectionState[
237
+ type as keyof typeof testConnectionState
238
+ ],
239
+ message: {
240
+ status: res.response.status,
241
+ message: res.response.message,
242
+ },
243
+ },
244
+ })
245
+
246
+ if (type === 'fetchTuning') {
247
+ formik.setFieldValue(
248
+ 'tuningParams',
249
+ formatTuningParams(res.response.tuningParams),
250
+ )
251
+ }
252
+
253
+ if (type === 'dockerImage' && res.response?.dbVersion) {
254
+ const currentDockerImage = dockerState.data.find(
255
+ (image) =>
256
+ Number(image.pg_major_version) === res.response?.dbVersion,
257
+ )
258
+
259
+ if (currentDockerImage) {
260
+ formik.setValues({
261
+ ...formik.values,
262
+ dockerImage: currentDockerImage.pg_major_version,
263
+ dockerPath: currentDockerImage.location,
264
+ dockerTag: currentDockerImage.tag,
265
+ })
266
+
267
+ setDockerState({
268
+ ...dockerState,
269
+ tags: dockerState.data
270
+ .map((image) => image.tag)
271
+ .filter((tag) =>
272
+ tag.startsWith(currentDockerImage.pg_major_version),
273
+ ),
274
+ })
275
+ }
276
+ }
277
+ } else if (res?.error) {
278
+ setTestConnectionState({
279
+ ...testConnectionState,
280
+ [type]: {
281
+ ...testConnectionState[
282
+ type as keyof typeof testConnectionState
283
+ ],
284
+ message: {
285
+ status: 'error',
286
+ message: res.error.message,
287
+ },
288
+ },
289
+ })
290
+ }
291
+ })
292
+ .catch((err) => {
293
+ setTestConnectionState({
294
+ ...testConnectionState,
295
+ [type]: {
296
+ ...testConnectionState[
297
+ type as keyof typeof testConnectionState
298
+ ],
299
+ error: err.message,
300
+ loading: false,
301
+ },
302
+ })
303
+ })
304
+ }
305
+ }
306
+
307
+ const handleModalClick = async () => {
308
+ await getFullConfig()
309
+ setIsModalOpen(true)
310
+ }
311
+
312
+ const handleDeleteChip = (
313
+ _: React.FormEvent<HTMLInputElement>,
314
+ uniqueValue: string,
315
+ id: string,
316
+ ) => {
317
+ if (formik.values[id as FormValuesKey]) {
318
+ let newValues = ''
319
+ const currentValues = uniqueChipValue(
320
+ String(formik.values[id as FormValuesKey]),
321
+ )
322
+ const splitValues = currentValues.split(' ')
323
+ const curDividers = String(formik.values[id as FormValuesKey]).match(
324
+ /[,(\s)(\n)(\r)(\t)(\r\n)]/gm,
325
+ )
326
+ for (let i in splitValues) {
327
+ if (curDividers && splitValues[i] !== uniqueValue) {
328
+ newValues =
329
+ newValues +
330
+ splitValues[i] +
331
+ (curDividers[i] ? curDividers[i] : '')
332
+ }
333
+ }
334
+ formik.setFieldValue(id, newValues)
335
+ }
336
+ }
337
+
338
+ const handleSelectPgOptions = (
339
+ e: React.ChangeEvent<HTMLInputElement>,
340
+ formikName: string,
341
+ ) => {
342
+ let pgValue = formik.values[formikName as FormValuesKey]
343
+ formik.setFieldValue(
344
+ formikName,
345
+ configData && configData[formikName as FormValuesKey],
346
+ )
347
+ const selectedPgOptions = imagePgOptions.filter(
348
+ (pg) => e.target.value === pg.optionType,
349
+ )
350
+
351
+ const setFormikPgValue = (name: string) => {
352
+ if (selectedPgOptions.length === 0) {
353
+ formik.setFieldValue(formikName, '')
354
+ }
355
+
356
+ selectedPgOptions.forEach((pg: PgOptionsType) => {
357
+ return (pg[name as keyof PgOptionsType] as string[]).forEach(
358
+ (addOption) => {
359
+ if (!String(pgValue)?.includes(addOption)) {
360
+ const addOptionWithSpace = addOption + ' '
361
+ formik.setFieldValue(
362
+ formikName,
363
+ (pgValue += addOptionWithSpace),
364
+ )
365
+ }
366
+ },
367
+ )
368
+ })
369
+ }
370
+
371
+ if (formikName === 'pgRestoreCustomOptions') {
372
+ setFormikPgValue('pgRestoreOptions')
373
+ } else {
374
+ setFormikPgValue('pgDumpOptions')
375
+ }
376
+ }
377
+
378
+ const fetchSeImages = async ({
379
+ dockerTag,
380
+ packageGroup,
381
+ initialRender,
382
+ }: {
383
+ dockerTag?: string
384
+ packageGroup: string
385
+ initialRender?: boolean
386
+ }) => {
387
+ setDockerState({
388
+ ...dockerState,
389
+ loading: true,
390
+ })
391
+ await getSeImages({
392
+ packageGroup,
393
+ }).then((data) => {
394
+ if (data) {
395
+ const seImagesMajorVersions = data
396
+ .map((image) => image.pg_major_version)
397
+ .filter((value, index, self) => self.indexOf(value) === index)
398
+ .sort((a, b) => Number(a) - Number(b))
399
+ const currentDockerImage = initialRender
400
+ ? formik.values.dockerImage
401
+ : seImagesMajorVersions.slice(-1)[0]
402
+
403
+ const currentPreloadLibraries =
404
+ data.find((image) => image.tag === dockerTag)?.pg_config_presets
405
+ ?.shared_preload_libraries ||
406
+ data[0]?.pg_config_presets?.shared_preload_libraries
407
+
408
+ setDockerState({
409
+ ...(initialRender
410
+ ? { images: seImagesMajorVersions }
411
+ : {
412
+ ...dockerState,
413
+ }),
414
+ error: '',
415
+ tags: data
416
+ .map((image) => image.tag)
417
+ .filter((tag) => tag.startsWith(currentDockerImage)),
418
+ locations: data
419
+ .map((image) => image.location)
420
+ .filter((location) => location?.includes(currentDockerImage)),
421
+ loading: false,
422
+ preloadLibraries: currentPreloadLibraries,
423
+ images: seImagesMajorVersions,
424
+ data,
425
+ })
426
+
427
+ formik.setValues({
428
+ ...formik.values,
429
+ dockerImage: currentDockerImage,
430
+ dockerImageType: packageGroup,
431
+ dockerTag: dockerTag
432
+ ? dockerTag
433
+ : data.map((image) => image.tag)[0],
434
+ dockerPath: initialRender
435
+ ? formik.values.dockerPath
436
+ : data.map((image) => image.location)[0],
437
+ sharedPreloadLibraries: currentPreloadLibraries || '',
438
+ })
439
+ } else {
440
+ setDockerState({
441
+ ...dockerState,
442
+ loading: false,
443
+ })
444
+ }
445
+ })
446
+ }
447
+
448
+ const handleDockerImageSelect = (
449
+ e: React.ChangeEvent<HTMLInputElement>,
450
+ ) => {
451
+ if (e.target.value === 'Generic Postgres') {
452
+ const genericImageVersions = genericDockerImages
453
+ .map((image) => image.pg_major_version)
454
+ .filter((value, index, self) => self.indexOf(value) === index)
455
+ .sort((a, b) => Number(a) - Number(b))
456
+ const currentDockerImage = genericImageVersions.slice(-1)[0]
457
+
458
+ setDockerState({
459
+ ...dockerState,
460
+ tags: genericDockerImages
461
+ .map((image) => image.tag)
462
+ .filter((tag) => tag.startsWith(currentDockerImage)),
463
+ locations: genericDockerImages
464
+ .map((image) => image.location)
465
+ .filter((location) => location?.includes(currentDockerImage)),
466
+ images: genericImageVersions,
467
+ data: genericDockerImages,
468
+ })
469
+
470
+ formik.setValues({
471
+ ...formik.values,
472
+ dockerImage: currentDockerImage,
473
+ dockerImageType: e.target.value,
474
+ dockerTag: genericDockerImages.map((image) => image.tag)[0],
475
+ dockerPath: genericDockerImages.map((image) => image.location)[0],
476
+ sharedPreloadLibraries:
477
+ 'pg_stat_statements,pg_stat_kcache,pg_cron,pgaudit,anon',
478
+ })
479
+ } else if (e.target.value === 'custom') {
480
+ formik.setValues({
481
+ ...formik.values,
482
+ dockerImage: '',
483
+ dockerPath: '',
484
+ dockerTag: '',
485
+ sharedPreloadLibraries: '',
486
+ dockerImageType: e.target.value,
487
+ })
488
+ } else {
489
+ formik.setValues({
490
+ ...formik.values,
491
+ dockerImageType: e.target.value,
492
+ })
493
+ fetchSeImages({
494
+ packageGroup: e.target.value,
495
+ })
496
+ }
497
+
498
+ handleSelectPgOptions(e, 'pgDumpCustomOptions')
499
+ handleSelectPgOptions(e, 'pgRestoreCustomOptions')
500
+ }
501
+
502
+ const handleDockerVersionSelect = (
503
+ e: React.ChangeEvent<HTMLInputElement>,
504
+ ) => {
505
+ if (formik.values.dockerImageType !== 'custom') {
506
+ const updatedDockerTags = dockerState.data
507
+ .map((image) => image.tag)
508
+ .filter((tag) => tag.startsWith(e.target.value))
509
+
510
+ setDockerState({
511
+ ...dockerState,
512
+ tags: updatedDockerTags,
513
+ })
514
+
515
+ const currentLocation = dockerState.data.find(
516
+ (image) => image.tag === updatedDockerTags[0],
517
+ )?.location as string
518
+
519
+ formik.setValues({
520
+ ...formik.values,
521
+ dockerTag: updatedDockerTags[0],
522
+ dockerImage: e.target.value,
523
+ dockerPath: currentLocation,
524
+ })
525
+ } else {
526
+ formik.setValues({
527
+ ...formik.values,
528
+ dockerImage: e.target.value,
529
+ dockerPath: e.target.value,
530
+ })
531
+ }
532
+ }
533
+
534
+ // Set initial data, empty string for password
535
+ useEffect(() => {
536
+ if (configData) {
537
+ for (const [key, value] of Object.entries(configData)) {
538
+ if (key !== 'password') {
539
+ formik.setFieldValue(key, value)
540
+ }
541
+
542
+ if (key === 'tuningParams') {
543
+ formik.setFieldValue(key, value)
544
+ }
545
+
546
+ if (customOrGenericImage(configData?.dockerImageType)) {
547
+ if (configData?.dockerImageType === 'Generic Postgres') {
548
+ const genericImageVersions = genericDockerImages
549
+ .map((image) => image.pg_major_version)
550
+ .filter((value, index, self) => self.indexOf(value) === index)
551
+ .sort((a, b) => Number(a) - Number(b))
552
+ const currentDockerImage =
553
+ genericDockerImages.filter(
554
+ (image) => image.location === configData?.dockerPath,
555
+ )[0] ||
556
+ genericDockerImages.filter((image) =>
557
+ configData?.dockerPath?.includes(image.pg_major_version),
558
+ )[0]
559
+
560
+ setDockerState({
561
+ ...dockerState,
562
+ tags: genericDockerImages
563
+ .map((image) => image.tag)
564
+ .filter((tag) =>
565
+ tag.startsWith(currentDockerImage.pg_major_version),
566
+ ),
567
+ images: genericImageVersions,
568
+ data: genericDockerImages,
569
+ })
570
+
571
+ formik.setFieldValue('dockerTag', currentDockerImage?.tag)
572
+ formik.setFieldValue(
573
+ 'dockerImage',
574
+ currentDockerImage.pg_major_version,
575
+ )
576
+ } else {
577
+ formik.setFieldValue('dockerImage', configData?.dockerPath)
578
+ }
579
+ }
580
+ }
581
+ }
582
+ }, [config])
583
+
584
+ useEffect(() => {
585
+ getEngine().then((res) => {
586
+ setDledition(String(res?.edition))
587
+ })
588
+ }, [])
589
+
590
+ useEffect(() => {
591
+ const initialFetch = async () => {
592
+ if (
593
+ formik.dirty &&
594
+ !isCeEdition &&
595
+ !customOrGenericImage(configData?.dockerImageType)
596
+ ) {
597
+ await getFullConfig().then(async (data) => {
598
+ if (data) {
599
+ await fetchSeImages({
600
+ packageGroup: configData?.dockerImageType as string,
601
+ dockerTag: configData?.dockerTag,
602
+ initialRender: true,
603
+ })
604
+ }
605
+ })
606
+ }
607
+ }
608
+ initialFetch()
609
+ }, [
610
+ formik.dirty,
611
+ configData?.dockerImageType,
612
+ configData?.dockerTag,
613
+ isCeEdition,
614
+ ])
615
+
616
+ return (
617
+ <div className={styles.root}>
618
+ <Snackbar
619
+ onClick={() => {
620
+ Boolean(dockerState.error)
621
+ ? setDockerState({
622
+ ...dockerState,
623
+ error: '',
624
+ })
625
+ : undefined
626
+ }}
627
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
628
+ open={
629
+ (isConfigurationDisabled || Boolean(dockerState.error)) &&
630
+ !isModalOpen
631
+ }
632
+ message={
633
+ Boolean(dockerState.error)
634
+ ? dockerState.error
635
+ : disableConfigModification
636
+ ? PREVENT_MODIFYING_MESSAGE
637
+ : NON_LOGICAL_RETRIEVAL_MESSAGE
638
+ }
639
+ className={styles.snackbar}
640
+ />
641
+ {!config && isConfigurationLoading ? (
642
+ <div className={styles.spinnerContainer}>
643
+ <Spinner size="lg" className={styles.spinner} />
644
+ </div>
645
+ ) : (
646
+ <Box>
647
+ <Header retrievalMode="logical" setOpen={handleModalClick} />
648
+ <Box>
649
+ <Box>
650
+ <FormControlLabel
651
+ control={
652
+ <Checkbox
653
+ name="debug"
654
+ checked={formik.values.debug}
655
+ disabled={isConfigurationDisabled}
656
+ onChange={(e) =>
657
+ formik.setFieldValue('debug', e.target.checked)
658
+ }
659
+ classes={{
660
+ root: classes.checkboxRoot,
661
+ }}
662
+ />
663
+ }
664
+ label={'Debug mode'}
665
+ />
666
+ </Box>
667
+ <Box mb={1} mt={1}>
668
+ <ConfigSectionTitle tag="retrieval" />
669
+ <Box mt={1}>
670
+ <Typography className={styles.subsection}>
671
+ Subsection "retrieval.spec.logicalDump"
672
+ </Typography>
673
+ <span className={classes.grayText}>
674
+ Source database credentials and dumping options.
675
+ </span>
676
+ <InputWithTooltip
677
+ label="source.connection.host *"
678
+ value={formik.values.host}
679
+ error={formik.errors.host}
680
+ tooltipText={tooltipText.host}
681
+ disabled={isConfigurationDisabled}
682
+ onChange={(e) =>
683
+ formik.setFieldValue('host', e.target.value)
684
+ }
685
+ />
686
+ <InputWithTooltip
687
+ label="source.connection.port *"
688
+ value={formik.values.port}
689
+ error={formik.errors.port}
690
+ tooltipText={tooltipText.port}
691
+ disabled={isConfigurationDisabled}
692
+ onChange={(e) =>
693
+ formik.setFieldValue('port', e.target.value)
694
+ }
695
+ />
696
+ <InputWithTooltip
697
+ label="source.connection.username *"
698
+ value={formik.values.username}
699
+ error={formik.errors.username}
700
+ tooltipText={tooltipText.username}
701
+ disabled={isConfigurationDisabled}
702
+ onChange={(e) =>
703
+ formik.setFieldValue('username', e.target.value)
704
+ }
705
+ />
706
+ <InputWithTooltip
707
+ type="password"
708
+ value={formik.values.password}
709
+ label="source.connection.password"
710
+ tooltipText={tooltipText.password}
711
+ disabled={isConfigurationDisabled}
712
+ onChange={(e) =>
713
+ formik.setFieldValue('password', e.target.value)
714
+ }
715
+ />
716
+ <InputWithTooltip
717
+ label="source.connection.dbname *"
718
+ value={formik.values.dbname}
719
+ error={formik.errors.dbname}
720
+ tooltipText={tooltipText.dbname}
721
+ disabled={isConfigurationDisabled}
722
+ onChange={(e) =>
723
+ formik.setFieldValue('dbname', e.target.value)
724
+ }
725
+ />
726
+ <InputWithChip
727
+ id="databases"
728
+ value={formik.values.databases}
729
+ label="Databases to copy"
730
+ tooltipText={tooltipText.databases}
731
+ handleDeleteChip={handleDeleteChip}
732
+ disabled={isConfigurationDisabled}
733
+ onChange={(e) =>
734
+ formik.setFieldValue('databases', e.target.value)
735
+ }
736
+ />
737
+ <Box mt={3} mb={3}>
738
+ <Button
739
+ variant="outlined"
740
+ color="secondary"
741
+ onClick={() => {
742
+ onTestConnectionClick({
743
+ type: 'default',
744
+ })
745
+ }}
746
+ disabled={
747
+ testConnectionState.default.loading ||
748
+ isConfigurationDisabled
749
+ }
750
+ >
751
+ Test connection
752
+ {testConnectionState.default.loading && (
753
+ <Spinner size="sm" className={styles.spinner} />
754
+ )}
755
+ </Button>
756
+ {testConnectionState.default.message.status ||
757
+ testConnectionState.default.error ? (
758
+ <ResponseMessage
759
+ type={
760
+ testConnectionState.default.error
761
+ ? 'error'
762
+ : testConnectionState.default.message.status
763
+ ? testConnectionState.default.message.status
764
+ : ''
765
+ }
766
+ message={
767
+ testConnectionState.default.error ||
768
+ testConnectionState.default.message.message
769
+ }
770
+ />
771
+ ) : null}
772
+ </Box>
773
+ <InputWithTooltip
774
+ label="pg_dump jobs"
775
+ value={formik.values.dumpParallelJobs}
776
+ tooltipText={tooltipText.dumpParallelJobs}
777
+ disabled={isConfigurationDisabled}
778
+ onChange={(e) =>
779
+ formik.setFieldValue('dumpParallelJobs', e.target.value)
780
+ }
781
+ />
782
+ <InputWithChip
783
+ value={formik.values.pgDumpCustomOptions}
784
+ label="pg_dump customOptions"
785
+ id="pgDumpCustomOptions"
786
+ tooltipText={tooltipText.pgDumpCustomOptions}
787
+ handleDeleteChip={handleDeleteChip}
788
+ disabled={isConfigurationDisabled}
789
+ onChange={(e) =>
790
+ formik.setFieldValue(
791
+ 'pgDumpCustomOptions',
792
+ e.target.value,
793
+ )
794
+ }
795
+ />
796
+ <FormControlLabel
797
+ style={{ maxWidth: 'max-content' }}
798
+ control={
799
+ <Checkbox
800
+ name="dumpIgnoreErrors"
801
+ checked={formik.values.dumpIgnoreErrors}
802
+ disabled={isConfigurationDisabled}
803
+ onChange={(e) =>
804
+ formik.setFieldValue(
805
+ 'dumpIgnoreErrors',
806
+ e.target.checked,
807
+ )
808
+ }
809
+ classes={{
810
+ root: classes.checkboxRoot,
811
+ }}
812
+ />
813
+ }
814
+ label={'Ignore errors during logical data dump'}
815
+ />
816
+ </Box>
817
+ </Box>
818
+ <Box mb={2} mt={1}>
819
+ <ConfigSectionTitle tag="databaseContainer" />
820
+ <span
821
+ className={classes.grayText}
822
+ style={{ margin: '0.5rem 0 1rem 0', display: 'block' }}
823
+ >
824
+ DBLab manages various database containers, such as clones.
825
+ This section defines default container settings.
826
+ </span>
827
+ <div>
828
+ <SelectWithTooltip
829
+ label="dockerImage - choose from the list *"
830
+ value={formik.values.dockerImageType}
831
+ error={Boolean(formik.errors.dockerImageType)}
832
+ tooltipText={tooltipText.dockerImageType}
833
+ disabled={isConfigurationDisabled || dockerState.loading}
834
+ items={filteredDockerImageOptions.map((image) => {
835
+ return {
836
+ value: image.type,
837
+ children: image.name,
838
+ }
839
+ })}
840
+ onChange={handleDockerImageSelect}
841
+ />
842
+ {formik.values.dockerImageType === 'custom' ? (
843
+ <InputWithTooltip
844
+ label="dockerImage *"
845
+ value={formik.values.dockerImage}
846
+ error={formik.errors.dockerImage}
847
+ tooltipText={tooltipText.dockerImage}
848
+ disabled={isConfigurationDisabled}
849
+ onChange={(e) => {
850
+ formik.setValues({
851
+ ...formik.values,
852
+ dockerImage: e.target.value,
853
+ dockerPath: e.target.value,
854
+ })
855
+ }}
856
+ />
857
+ ) : (
858
+ <>
859
+ <SelectWithTooltip
860
+ label="dockerImage - Postgres major version *"
861
+ value={formik.values.dockerImage}
862
+ error={Boolean(formik.errors.dockerImage)}
863
+ tooltipText={tooltipText.dockerImage}
864
+ disabled={
865
+ isConfigurationDisabled ||
866
+ dockerState.loading ||
867
+ !dockerState.images.length
868
+ }
869
+ loading={dockerState.loading}
870
+ items={dockerState.images
871
+ .slice()
872
+ .reverse()
873
+ .map((image) => {
874
+ return {
875
+ value: image,
876
+ children: image,
877
+ }
878
+ })}
879
+ onChange={handleDockerVersionSelect}
880
+ />
881
+ <Box mt={0.5} mb={2}>
882
+ <Button
883
+ variant="outlined"
884
+ color="secondary"
885
+ onClick={() => {
886
+ onTestConnectionClick({
887
+ type: 'dockerImage',
888
+ })
889
+ }}
890
+ disabled={
891
+ testConnectionState.dockerImage.loading ||
892
+ isConfigurationDisabled
893
+ }
894
+ >
895
+ Get version from source
896
+ {testConnectionState.dockerImage.loading && (
897
+ <Spinner size="sm" className={styles.spinner} />
898
+ )}
899
+ </Button>
900
+ {testConnectionState.dockerImage.message.status ===
901
+ 'error' || testConnectionState.dockerImage.error ? (
902
+ <ResponseMessage
903
+ type={
904
+ testConnectionState.dockerImage.error ||
905
+ testConnectionState.dockerImage.message
906
+ ? 'error'
907
+ : ''
908
+ }
909
+ message={
910
+ testConnectionState.dockerImage.error ||
911
+ testConnectionState.dockerImage.message.message
912
+ }
913
+ />
914
+ ) : null}
915
+ </Box>
916
+ <SelectWithTooltip
917
+ label="dockerImage - tag *"
918
+ value={formik.values.dockerTag}
919
+ error={Boolean(formik.errors.dockerTag)}
920
+ tooltipText={tooltipText.dockerTag}
921
+ disabled={
922
+ isConfigurationDisabled ||
923
+ dockerState.loading ||
924
+ !dockerState.tags.length
925
+ }
926
+ loading={dockerState.loading}
927
+ onChange={(e) => {
928
+ const currentLocation = dockerState.data.find(
929
+ (image) => image.tag === e.target.value,
930
+ )?.location as string
931
+
932
+ formik.setValues({
933
+ ...formik.values,
934
+ dockerTag: e.target.value,
935
+ dockerPath: currentLocation,
936
+ })
937
+ }}
938
+ items={dockerState.tags.map((image) => {
939
+ return {
940
+ value: image,
941
+ children: image,
942
+ }
943
+ })}
944
+ />
945
+ </>
946
+ )}
947
+ <Typography paragraph>
948
+ Cannot find your image? Reach out to support:{' '}
949
+ <a
950
+ href={'https://postgres.ai/contact'}
951
+ target="_blank"
952
+ className={styles.externalLink}
953
+ >
954
+ https://postgres.ai/contact
955
+ <ExternalIcon className={styles.externalIcon} />
956
+ </a>
957
+ </Typography>
958
+ </div>
959
+ </Box>
960
+ <Box mb={3}>
961
+ <ConfigSectionTitle tag="databaseConfigs" />
962
+ <span
963
+ className={classes.grayText}
964
+ style={{ marginTop: '0.5rem', display: 'block' }}
965
+ >
966
+ Default Postgres configuration used for all Postgres instances
967
+ running in containers managed by DBLab.
968
+ </span>
969
+ <InputWithTooltip
970
+ type="textarea"
971
+ label="shared_buffers parameter"
972
+ value={formik.values.sharedBuffers}
973
+ tooltipText={tooltipText.sharedBuffers}
974
+ disabled={isConfigurationDisabled}
975
+ onChange={(e) =>
976
+ formik.setFieldValue('sharedBuffers', e.target.value)
977
+ }
978
+ />
979
+ <InputWithTooltip
980
+ type="textarea"
981
+ label="shared_preload_libraries"
982
+ value={formik.values.sharedPreloadLibraries}
983
+ tooltipText={tooltipText.sharedPreloadLibraries}
984
+ disabled={isConfigurationDisabled}
985
+ onChange={(e) =>
986
+ formik.setFieldValue(
987
+ 'sharedPreloadLibraries',
988
+ e.target.value,
989
+ )
990
+ }
991
+ />
992
+ <InputWithTooltip
993
+ type="textarea"
994
+ label="Query tuning parameters"
995
+ value={
996
+ typeof formik.values.tuningParams === 'object'
997
+ ? Object.entries(
998
+ formik.values.tuningParams as Record<string, string>,
999
+ )
1000
+ .map(([key, value]) => `${key}=${value}`)
1001
+ .join('\n')
1002
+ : formik.values.tuningParams
1003
+ }
1004
+ tooltipText={tooltipText.tuningParams}
1005
+ disabled={isConfigurationDisabled}
1006
+ onChange={(e) =>
1007
+ formik.setFieldValue('tuningParams', e.target.value)
1008
+ }
1009
+ />
1010
+ <Button
1011
+ variant="outlined"
1012
+ color="secondary"
1013
+ onClick={() => {
1014
+ onTestConnectionClick({
1015
+ type: 'fetchTuning',
1016
+ })
1017
+ }}
1018
+ disabled={
1019
+ testConnectionState.fetchTuning.loading ||
1020
+ isConfigurationDisabled
1021
+ }
1022
+ >
1023
+ Get from source database
1024
+ {testConnectionState.fetchTuning.loading && (
1025
+ <Spinner size="sm" className={styles.spinner} />
1026
+ )}
1027
+ </Button>
1028
+ {testConnectionState.fetchTuning.message.status === 'error' ||
1029
+ testConnectionState.fetchTuning.error ? (
1030
+ <ResponseMessage
1031
+ type={
1032
+ testConnectionState.fetchTuning.error ||
1033
+ testConnectionState.fetchTuning.message
1034
+ ? 'error'
1035
+ : ''
1036
+ }
1037
+ message={
1038
+ testConnectionState.fetchTuning.error ||
1039
+ testConnectionState.fetchTuning.message.message
1040
+ }
1041
+ />
1042
+ ) : null}
1043
+ </Box>
1044
+ <Box>
1045
+ <Box>
1046
+ <Typography className={styles.subsection}>
1047
+ Subsection "retrieval.spec.logicalRestore"
1048
+ </Typography>
1049
+ <span className={classes.grayText}>Restoring options.</span>
1050
+ </Box>
1051
+ <InputWithTooltip
1052
+ label="pg_restore jobs"
1053
+ value={formik.values.restoreParallelJobs}
1054
+ tooltipText={tooltipText.restoreParallelJobs}
1055
+ disabled={isConfigurationDisabled}
1056
+ onChange={(e) =>
1057
+ formik.setFieldValue('restoreParallelJobs', e.target.value)
1058
+ }
1059
+ />
1060
+ <InputWithChip
1061
+ value={formik.values.pgRestoreCustomOptions}
1062
+ label="pg_restore customOptions"
1063
+ id="pgRestoreCustomOptions"
1064
+ tooltipText={tooltipText.pgRestoreCustomOptions}
1065
+ handleDeleteChip={handleDeleteChip}
1066
+ disabled={isConfigurationDisabled}
1067
+ onChange={(e) =>
1068
+ formik.setFieldValue(
1069
+ 'pgRestoreCustomOptions',
1070
+ e.target.value,
1071
+ )
1072
+ }
1073
+ />
1074
+ <FormControlLabel
1075
+ style={{ maxWidth: 'max-content' }}
1076
+ control={
1077
+ <Checkbox
1078
+ name="restoreIgnoreErrors"
1079
+ checked={formik.values.restoreIgnoreErrors}
1080
+ disabled={isConfigurationDisabled}
1081
+ onChange={(e) =>
1082
+ formik.setFieldValue(
1083
+ 'restoreIgnoreErrors',
1084
+ e.target.checked,
1085
+ )
1086
+ }
1087
+ classes={{
1088
+ root: classes.checkboxRoot,
1089
+ }}
1090
+ />
1091
+ }
1092
+ label={'Ignore errors during logical data restore'}
1093
+ />
1094
+ </Box>
1095
+ <Box mt={1}>
1096
+ <Typography className={styles.subsection}>
1097
+ Subsection "retrieval.refresh"
1098
+ </Typography>
1099
+ </Box>
1100
+ <span className={classes.grayText}>
1101
+ Define full data refresh on schedule. The process requires at
1102
+ least one additional filesystem mount point. The schedule is to
1103
+ be specified using{' '}
1104
+ <a
1105
+ href="https://en.wikipedia.org/wiki/Cron#Overview"
1106
+ target="_blank"
1107
+ className={styles.externalLink}
1108
+ >
1109
+ crontab format
1110
+ <ExternalIcon className={styles.externalIcon} />
1111
+ </a>
1112
+ .
1113
+ </span>
1114
+ <InputWithTooltip
1115
+ label="timetable"
1116
+ value={formik.values.timetable}
1117
+ tooltipText={tooltipText.timetable}
1118
+ disabled={isConfigurationDisabled}
1119
+ onChange={(e) =>
1120
+ formik.setFieldValue('timetable', e.target.value)
1121
+ }
1122
+ />
1123
+ </Box>
1124
+ <Box
1125
+ mt={2}
1126
+ mb={2}
1127
+ sx={{
1128
+ display: 'flex',
1129
+ alignItems: 'center',
1130
+ }}
1131
+ >
1132
+ <Button
1133
+ variant="contained"
1134
+ color="secondary"
1135
+ onClick={() => {
1136
+ formik.submitForm().then(() => {
1137
+ scrollToField()
1138
+ })
1139
+ }}
1140
+ disabled={formik.isSubmitting || isConfigurationDisabled}
1141
+ >
1142
+ Apply changes
1143
+ {formik.isSubmitting && (
1144
+ <Spinner size="sm" className={styles.spinner} />
1145
+ )}
1146
+ </Button>
1147
+ <Box sx={{ px: 2 }}>
1148
+ <Button
1149
+ variant="outlined"
1150
+ color="secondary"
1151
+ onClick={() => switchActiveTab(null, 0)}
1152
+ >
1153
+ Cancel
1154
+ </Button>
1155
+ </Box>
1156
+ </Box>
1157
+ {(submitState.status && submitState.response) || configError ? (
1158
+ <ResponseMessage
1159
+ type={configError ? 'error' : submitState.status}
1160
+ message={configError || submitState.response}
1161
+ />
1162
+ ) : null}
1163
+ </Box>
1164
+ )}
1165
+ <Modal
1166
+ title={<ModalTitle />}
1167
+ onClose={() => setIsModalOpen(false)}
1168
+ isOpen={isModalOpen}
1169
+ size="xl"
1170
+ >
1171
+ <Editor
1172
+ height="70vh"
1173
+ width="100%"
1174
+ defaultLanguage="yaml"
1175
+ value={getFullConfigError ? getFullConfigError : fullConfig}
1176
+ loading={<StubSpinner />}
1177
+ theme="vs-light"
1178
+ options={{ domReadOnly: true, readOnly: true }}
1179
+ />
1180
+ </Modal>
1181
+ </div>
1182
+ )
1183
+ },
1184
+ )