@postgres.ai/shared 4.0.2-pr-1149 → 4.0.2-pr-1148.1

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 (35) hide show
  1. package/package.json +1 -1
  2. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
  3. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
  4. package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
  5. package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
  6. package/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
  7. package/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
  8. package/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
  9. package/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
  10. package/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
  11. package/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
  12. package/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +11 -0
  13. package/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
  14. package/pages/Instance/Configuration/SimpleMode/index.d.ts +14 -0
  15. package/pages/Instance/Configuration/SimpleMode/index.js +107 -0
  16. package/pages/Instance/Configuration/configMode.d.ts +2 -0
  17. package/pages/Instance/Configuration/configMode.js +7 -0
  18. package/pages/Instance/Configuration/configOptions.d.ts +6 -0
  19. package/pages/Instance/Configuration/configOptions.js +48 -0
  20. package/pages/Instance/Configuration/connectionString.d.ts +20 -0
  21. package/pages/Instance/Configuration/connectionString.js +129 -0
  22. package/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
  23. package/pages/Instance/Configuration/dockerCatalog.js +19 -0
  24. package/pages/Instance/Configuration/index.d.ts +1 -2
  25. package/pages/Instance/Configuration/index.js +115 -86
  26. package/pages/Instance/Configuration/useForm.d.ts +20 -0
  27. package/pages/Instance/Configuration/useForm.js +126 -5
  28. package/pages/Instance/Configuration/utils/index.js +1 -17
  29. package/pages/Instance/index.js +1 -3
  30. package/pages/Instance/stores/Main.d.ts +20 -0
  31. package/pages/Instance/stores/Main.js +9 -0
  32. package/types/api/endpoints/probeSource.d.ts +32 -0
  33. package/types/api/endpoints/probeSource.js +1 -0
  34. package/types/api/entities/config.d.ts +32 -0
  35. package/types/api/entities/config.js +45 -18
@@ -5,10 +5,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
5
5
  * Unauthorized copying of this file, via any small is strictly prohibited
6
6
  *--------------------------------------------------------------------------
7
7
  */
8
- import { useState, useEffect, useMemo } from 'react';
8
+ import { useState, useEffect, useMemo, useRef } from 'react';
9
9
  import { observer } from 'mobx-react-lite';
10
10
  import Editor from '@monaco-editor/react';
11
- import { Checkbox, FormControlLabel, Typography, Snackbar, makeStyles, Button, } from '@material-ui/core';
11
+ import { Checkbox, FormControlLabel, Typography, Snackbar, makeStyles, Button, Tab, Tabs, Radio, RadioGroup, } from '@material-ui/core';
12
12
  import Box from '@mui/material/Box';
13
13
  import { Modal } from '@postgres.ai/shared/components/Modal';
14
14
  import { StubSpinner } from '@postgres.ai/shared/components/StubSpinner';
@@ -22,9 +22,11 @@ import { ConfigSectionTitle, Header, ModalTitle } from './Header';
22
22
  import { dockerImageOptions, imagePgOptions } from './configOptions';
23
23
  import { uniqueChipValue, customOrGenericImage, createEnhancedDockerImages, } from './utils';
24
24
  import { SelectWithTooltip, InputWithChip, InputWithTooltip, } from './InputWithTooltip';
25
+ import { SimpleMode, buildProjectionFromProposed } from './SimpleMode';
26
+ import { getInitialConfigMode } from './configMode';
27
+ import { PhysicalMode } from './PhysicalMode';
25
28
  import styles from './styles.module.scss';
26
29
  import { formatTuningParams, formatTuningParamsToObj, } from '@postgres.ai/shared/types/api/endpoints/testDbSource';
27
- const NON_LOGICAL_RETRIEVAL_MESSAGE = 'Configuration editing is only available in logical mode';
28
30
  const PREVENT_MODIFYING_MESSAGE = 'Editing is disabled by admin';
29
31
  const useStyles = makeStyles({
30
32
  checkboxRoot: {
@@ -35,17 +37,31 @@ const useStyles = makeStyles({
35
37
  fontSize: '12px',
36
38
  },
37
39
  }, { index: 1 });
38
- export const Configuration = observer(({ instanceId, switchActiveTab, reload, isConfigurationActive, disableConfigModification, }) => {
40
+ export const Configuration = observer(({ instanceId, switchActiveTab, reload, disableConfigModification, }) => {
41
+ var _a, _b;
39
42
  const classes = useStyles();
40
43
  const stores = useStores();
41
44
  const { config, isConfigurationLoading, updateConfig, getSeImages, fullConfig, testDbSource, configError, getFullConfig, getFullConfigError, getEngine, } = stores.main;
42
45
  const configData = config && JSON.parse(JSON.stringify(config));
43
- const isConfigurationDisabled = !isConfigurationActive || disableConfigModification;
46
+ const isConfigurationDisabled = disableConfigModification;
44
47
  const [dleEdition, setDledition] = useState('');
45
48
  const isCeEdition = dleEdition === 'community';
46
49
  const filteredDockerImageOptions = isCeEdition
47
50
  ? dockerImageOptions.filter((option) => option.type === 'custom' || option.type === 'Generic Postgres')
48
51
  : dockerImageOptions;
52
+ const [userPickedMode, setUserPickedMode] = useState(null);
53
+ // Seeded lazily — only once configData transitions from null to non-null.
54
+ // Prevents the Simple/Expert default from flipping mid-session when an
55
+ // Apply triggers a refetch and the recomputed default would differ.
56
+ const [initialMode, setInitialMode] = useState(null);
57
+ useEffect(() => {
58
+ if (initialMode === null && configData) {
59
+ setInitialMode(getInitialConfigMode(configData.host, configData.retrievalMode));
60
+ }
61
+ }, [configData, initialMode]);
62
+ const configMode = (_a = userPickedMode !== null && userPickedMode !== void 0 ? userPickedMode : initialMode) !== null && _a !== void 0 ? _a : 'simple';
63
+ const setConfigMode = setUserPickedMode;
64
+ const portInitFromConfig = useRef(false);
49
65
  const [isModalOpen, setIsModalOpen] = useState(false);
50
66
  const [submitState, setSubmitState] = useState({
51
67
  status: '',
@@ -97,6 +113,8 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
97
113
  });
98
114
  await updateConfig({
99
115
  ...values,
116
+ // Preserve configs that originally had no port: key.
117
+ ...(omitPortOnSubmit && { port: '' }),
100
118
  tuningParams: formatTuningParamsToObj(values.tuningParams),
101
119
  }, instanceId).then((response) => {
102
120
  if (response === null || response === void 0 ? void 0 : response.ok) {
@@ -107,7 +125,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
107
125
  }
108
126
  });
109
127
  };
110
- const [{ formik, connectionData, isConnectionDataValid }] = useForm(onSubmit);
128
+ const [{ formik, connectionData, isConnectionDataValid, connectionString, connectionStringError, onConnectionStringChange, markPortInitialState, omitPortOnSubmit, },] = useForm(onSubmit);
111
129
  // Memoized enhanced Docker images to avoid recreation on every render
112
130
  // This combines predefined images with any custom image from configuration
113
131
  const enhancedDockerImages = useMemo(() => {
@@ -413,6 +431,10 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
413
431
  // Set initial data, empty string for password
414
432
  useEffect(() => {
415
433
  if (configData) {
434
+ if (!portInitFromConfig.current) {
435
+ markPortInitialState(configData.port == null || configData.port === '');
436
+ portInitFromConfig.current = true;
437
+ }
416
438
  for (const [key, value] of Object.entries(configData)) {
417
439
  if (key !== 'password') {
418
440
  formik.setFieldValue(key, value);
@@ -494,91 +516,98 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
494
516
  }, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, open: (isConfigurationDisabled || Boolean(dockerState.error)) &&
495
517
  !isModalOpen, message: Boolean(dockerState.error)
496
518
  ? dockerState.error
497
- : disableConfigModification
498
- ? PREVENT_MODIFYING_MESSAGE
499
- : NON_LOGICAL_RETRIEVAL_MESSAGE, className: styles.snackbar }), !config && isConfigurationLoading ? (_jsx("div", { className: styles.spinnerContainer, children: _jsx(Spinner, { size: "lg", className: styles.spinner }) })) : (_jsxs(Box, { children: [_jsx(Header, { retrievalMode: "logical", setOpen: handleModalClick }), _jsxs(Box, { children: [_jsx(Box, { children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "debug", checked: formik.values.debug, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('debug', e.target.checked), classes: {
500
- root: classes.checkboxRoot,
501
- } }), label: 'Debug mode' }) }), _jsxs(Box, { mb: 1, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "retrieval" }), _jsxs(Box, { mt: 1, children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalDump\"" }), _jsx("span", { className: classes.grayText, children: "Source database credentials and dumping options." }), _jsx(InputWithTooltip, { label: "source.connection.host *", value: formik.values.host, error: formik.errors.host, tooltipText: tooltipText.host, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('host', e.target.value) }), _jsx(InputWithTooltip, { label: "source.connection.port *", value: formik.values.port, error: formik.errors.port, tooltipText: tooltipText.port, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('port', e.target.value) }), _jsx(InputWithTooltip, { label: "source.connection.username *", value: formik.values.username, error: formik.errors.username, tooltipText: tooltipText.username, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('username', e.target.value) }), _jsx(InputWithTooltip, { type: "password", value: formik.values.password, label: "source.connection.password", tooltipText: tooltipText.password, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('password', e.target.value) }), _jsx(InputWithTooltip, { label: "source.connection.dbname *", value: formik.values.dbname, error: formik.errors.dbname, tooltipText: tooltipText.dbname, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dbname', e.target.value) }), _jsx(InputWithChip, { id: "databases", value: formik.values.databases, label: "Databases to copy", tooltipText: tooltipText.databases, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('databases', e.target.value) }), _jsxs(Box, { mt: 3, mb: 3, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
502
- onTestConnectionClick({
503
- type: 'default',
504
- });
505
- }, disabled: testConnectionState.default.loading ||
506
- isConfigurationDisabled, children: ["Test connection", testConnectionState.default.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.default.message.status ||
507
- testConnectionState.default.error ? (_jsx(ResponseMessage, { type: testConnectionState.default.error
508
- ? 'error'
509
- : testConnectionState.default.message.status
510
- ? testConnectionState.default.message.status
511
- : '', message: testConnectionState.default.error ||
512
- testConnectionState.default.message.message })) : null] }), _jsx(InputWithTooltip, { label: "pg_dump jobs", value: formik.values.dumpParallelJobs, tooltipText: tooltipText.dumpParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgDumpCustomOptions, label: "pg_dump customOptions", id: "pgDumpCustomOptions", tooltipText: tooltipText.pgDumpCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgDumpCustomOptions', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "dumpIgnoreErrors", checked: formik.values.dumpIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpIgnoreErrors', e.target.checked), classes: {
513
- root: classes.checkboxRoot,
514
- } }), label: 'Ignore errors during logical data dump' })] })] }), _jsxs(Box, { mb: 2, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "databaseContainer" }), _jsx("span", { className: classes.grayText, style: { margin: '0.5rem 0 1rem 0', display: 'block' }, children: "DBLab manages various database containers, such as clones. This section defines default container settings." }), _jsxs("div", { children: [_jsx(SelectWithTooltip, { label: "dockerImage - choose from the list *", value: formik.values.dockerImageType, error: Boolean(formik.errors.dockerImageType), tooltipText: tooltipText.dockerImageType, disabled: isConfigurationDisabled || dockerState.loading, items: filteredDockerImageOptions.map((image) => {
515
- return {
516
- value: image.type,
517
- children: image.name,
518
- };
519
- }), onChange: handleDockerImageSelect }), formik.values.dockerImageType === 'custom' ? (_jsx(InputWithTooltip, { label: "dockerImage *", value: formik.values.dockerImage, error: formik.errors.dockerImage, tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled, onChange: (e) => {
520
- formik.setValues({
521
- ...formik.values,
522
- dockerImage: e.target.value,
523
- dockerPath: e.target.value,
524
- });
525
- } })) : (_jsxs(_Fragment, { children: [_jsx(SelectWithTooltip, { label: "dockerImage - Postgres major version *", value: formik.values.dockerImage, error: Boolean(formik.errors.dockerImage), tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled ||
526
- dockerState.loading ||
527
- !dockerState.images.length, loading: dockerState.loading, items: dockerState.images
528
- .slice()
529
- .reverse()
530
- .map((image) => {
531
- return {
532
- value: image,
533
- children: image,
534
- };
535
- }), onChange: handleDockerVersionSelect }), _jsxs(Box, { mt: 0.5, mb: 2, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
519
+ : PREVENT_MODIFYING_MESSAGE, className: styles.snackbar }), !config && isConfigurationLoading ? (_jsx("div", { className: styles.spinnerContainer, children: _jsx(Spinner, { size: "lg", className: styles.spinner }) })) : (_jsxs(Box, { children: [_jsx(Header, { retrievalMode: formik.values.retrievalMode, setOpen: handleModalClick }), _jsxs(Tabs, { value: configMode, onChange: (_, value) => setConfigMode(value), indicatorColor: "primary", textColor: "primary", "aria-label": "Configuration mode", children: [_jsx(Tab, { value: "simple", label: "Simple" }), _jsx(Tab, { value: "expert", label: "Expert" })] }), configMode === 'simple' ? (_jsx(SimpleMode, { instanceId: instanceId, disabled: isConfigurationDisabled, onApplied: switchTab, onEdit: (proposed, password) => {
520
+ const projection = buildProjectionFromProposed(proposed, password);
521
+ formik.setValues({ ...formik.values, ...projection });
522
+ setConfigMode('expert');
523
+ } })) : null, configMode === 'expert' ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Box, { children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "debug", checked: formik.values.debug, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('debug', e.target.checked), classes: {
524
+ root: classes.checkboxRoot,
525
+ } }), label: 'Debug mode' }) }), _jsxs(Box, { mb: 1, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "retrieval" }), _jsxs(Box, { mt: 1, mb: 1, children: [_jsx(Typography, { className: styles.subsection, children: "Retrieval mode" }), _jsxs(RadioGroup, { row: true, "aria-label": "retrieval mode", value: formik.values.retrievalMode, onChange: (_, value) => {
526
+ formik.setFieldValue('retrievalMode', value);
527
+ }, children: [_jsx(FormControlLabel, { value: "logical", control: _jsx(Radio, { disabled: isConfigurationDisabled }), label: "Logical (dump/restore)" }), _jsx(FormControlLabel, { value: "physical", control: _jsx(Radio, { disabled: isConfigurationDisabled }), label: "Physical (WAL-G / pgBackRest)" })] })] }), formik.values.retrievalMode === 'physical' && (_jsx(PhysicalMode, { values: formik.values, onChange: (key, value) => formik.setFieldValue(key, value, key === 'physicalEnvs'), disabled: isConfigurationDisabled, envsKeyErrors: (_b = formik.errors.physicalEnvs) === null || _b === void 0 ? void 0 : _b.map((row) => row === null || row === void 0 ? void 0 : row.key) })), formik.values.retrievalMode === 'logical' && (_jsxs(Box, { mt: 1, children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalDump\"" }), _jsx("span", { className: classes.grayText, children: "Source database credentials and dumping options." }), _jsx(InputWithTooltip, { label: "Connection string *", value: connectionString, error: connectionStringError ||
528
+ formik.errors.host ||
529
+ formik.errors.username ||
530
+ formik.errors.dbname, tooltipText: () => (_jsxs(_Fragment, { children: ["URI form: ", _jsx("code", { children: "postgres://user@host:5432/dbname" }), ". DSN form is also accepted. Do not include the password here \u2014 use the Password field below."] })), disabled: isConfigurationDisabled, onChange: (e) => onConnectionStringChange(e.target.value) }), _jsx(Box, { mt: 0.5, mb: 1, children: _jsxs("span", { className: classes.grayText, "data-testid": "connection-string-parsed", children: ["host: ", formik.values.host || '', " | port:", ' ', formik.values.port || '5432 (default)', " | user:", ' ', formik.values.username || '—', " | dbname:", ' ', formik.values.dbname || '—'] }) }), _jsx(InputWithTooltip, { type: "password", value: formik.values.password, label: "source.connection.password", tooltipText: tooltipText.password, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('password', e.target.value) }), _jsx(InputWithChip, { id: "databases", value: formik.values.databases, label: "Databases to copy", tooltipText: tooltipText.databases, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('databases', e.target.value) }), _jsxs(Box, { mt: 3, mb: 3, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
536
531
  onTestConnectionClick({
537
- type: 'dockerImage',
532
+ type: 'default',
538
533
  });
539
- }, disabled: testConnectionState.dockerImage.loading ||
540
- isConfigurationDisabled, children: ["Get version from source", testConnectionState.dockerImage.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.dockerImage.message.status ===
541
- 'error' || testConnectionState.dockerImage.error ? (_jsx(ResponseMessage, { type: testConnectionState.dockerImage.error ||
542
- testConnectionState.dockerImage.message
534
+ }, disabled: testConnectionState.default.loading ||
535
+ isConfigurationDisabled, children: ["Test connection", testConnectionState.default.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.default.message.status ||
536
+ testConnectionState.default.error ? (_jsx(ResponseMessage, { type: testConnectionState.default.error
543
537
  ? 'error'
544
- : '', message: testConnectionState.dockerImage.error ||
545
- testConnectionState.dockerImage.message.message })) : null] }), _jsx(SelectWithTooltip, { label: "dockerImage - tag *", value: formik.values.dockerTag, error: Boolean(formik.errors.dockerTag), tooltipText: tooltipText.dockerTag, disabled: isConfigurationDisabled ||
546
- dockerState.loading ||
547
- !dockerState.tags.length, loading: dockerState.loading, onChange: (e) => {
548
- var _a;
549
- const currentLocation = (_a = dockerState.data.find((image) => image.tag === e.target.value)) === null || _a === void 0 ? void 0 : _a.location;
538
+ : testConnectionState.default.message.status
539
+ ? testConnectionState.default.message.status
540
+ : '', message: testConnectionState.default.error ||
541
+ testConnectionState.default.message.message })) : null] }), _jsx(InputWithTooltip, { label: "pg_dump jobs", value: formik.values.dumpParallelJobs, tooltipText: tooltipText.dumpParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgDumpCustomOptions, label: "pg_dump customOptions", id: "pgDumpCustomOptions", tooltipText: tooltipText.pgDumpCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgDumpCustomOptions', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "dumpIgnoreErrors", checked: formik.values.dumpIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpIgnoreErrors', e.target.checked), classes: {
542
+ root: classes.checkboxRoot,
543
+ } }), label: 'Ignore errors during logical data dump' })] }))] }), _jsxs(Box, { mb: 2, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "databaseContainer" }), _jsx("span", { className: classes.grayText, style: { margin: '0.5rem 0 1rem 0', display: 'block' }, children: "DBLab manages various database containers, such as clones. This section defines default container settings." }), _jsxs("div", { children: [_jsx(SelectWithTooltip, { label: "dockerImage - choose from the list *", value: formik.values.dockerImageType, error: Boolean(formik.errors.dockerImageType), tooltipText: tooltipText.dockerImageType, disabled: isConfigurationDisabled || dockerState.loading, items: filteredDockerImageOptions.map((image) => {
544
+ return {
545
+ value: image.type,
546
+ children: image.name,
547
+ };
548
+ }), onChange: handleDockerImageSelect }), formik.values.dockerImageType === 'custom' ? (_jsx(InputWithTooltip, { label: "dockerImage *", value: formik.values.dockerImage, error: formik.errors.dockerImage, tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled, onChange: (e) => {
550
549
  formik.setValues({
551
550
  ...formik.values,
552
- dockerTag: e.target.value,
553
- dockerPath: currentLocation,
551
+ dockerImage: e.target.value,
552
+ dockerPath: e.target.value,
554
553
  });
555
- }, items: dockerState.tags.map((image) => {
556
- return {
557
- value: image,
558
- children: image,
559
- };
560
- }) })] })), _jsxs(Typography, { paragraph: true, children: ["Cannot find your image? Reach out to support:", ' ', _jsxs("a", { href: 'https://postgres.ai/contact', target: "_blank", className: styles.externalLink, children: ["https://postgres.ai/contact", _jsx(ExternalIcon, { className: styles.externalIcon })] })] })] })] }), _jsxs(Box, { mb: 3, children: [_jsx(ConfigSectionTitle, { tag: "databaseConfigs" }), _jsx("span", { className: classes.grayText, style: { marginTop: '0.5rem', display: 'block' }, children: "Default PostgreSQL configuration used for all PostgreSQL instances running in containers managed by DBLab." }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_buffers parameter", value: formik.values.sharedBuffers, tooltipText: tooltipText.sharedBuffers, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedBuffers', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_preload_libraries", value: formik.values.sharedPreloadLibraries, tooltipText: tooltipText.sharedPreloadLibraries, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedPreloadLibraries', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Query tuning parameters", value: typeof formik.values.tuningParams === 'object'
561
- ? Object.entries(formik.values.tuningParams)
562
- .map(([key, value]) => `${key}=${value}`)
563
- .join('\n')
564
- : formik.values.tuningParams, tooltipText: tooltipText.tuningParams, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('tuningParams', e.target.value) }), _jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
565
- onTestConnectionClick({
566
- type: 'fetchTuning',
554
+ } })) : (_jsxs(_Fragment, { children: [_jsx(SelectWithTooltip, { label: "dockerImage - Postgres major version *", value: formik.values.dockerImage, error: Boolean(formik.errors.dockerImage), tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled ||
555
+ dockerState.loading ||
556
+ !dockerState.images.length, loading: dockerState.loading, items: dockerState.images
557
+ .slice()
558
+ .reverse()
559
+ .map((image) => {
560
+ return {
561
+ value: image,
562
+ children: image,
563
+ };
564
+ }), onChange: handleDockerVersionSelect }), _jsxs(Box, { mt: 0.5, mb: 2, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
565
+ onTestConnectionClick({
566
+ type: 'dockerImage',
567
+ });
568
+ }, disabled: testConnectionState.dockerImage.loading ||
569
+ isConfigurationDisabled, children: ["Get version from source", testConnectionState.dockerImage.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.dockerImage.message.status ===
570
+ 'error' || testConnectionState.dockerImage.error ? (_jsx(ResponseMessage, { type: testConnectionState.dockerImage.error ||
571
+ testConnectionState.dockerImage.message
572
+ ? 'error'
573
+ : '', message: testConnectionState.dockerImage.error ||
574
+ testConnectionState.dockerImage.message.message })) : null] }), _jsx(SelectWithTooltip, { label: "dockerImage - tag *", value: formik.values.dockerTag, error: Boolean(formik.errors.dockerTag), tooltipText: tooltipText.dockerTag, disabled: isConfigurationDisabled ||
575
+ dockerState.loading ||
576
+ !dockerState.tags.length, loading: dockerState.loading, onChange: (e) => {
577
+ var _a;
578
+ const currentLocation = (_a = dockerState.data.find((image) => image.tag === e.target.value)) === null || _a === void 0 ? void 0 : _a.location;
579
+ formik.setValues({
580
+ ...formik.values,
581
+ dockerTag: e.target.value,
582
+ dockerPath: currentLocation,
583
+ });
584
+ }, items: dockerState.tags.map((image) => {
585
+ return {
586
+ value: image,
587
+ children: image,
588
+ };
589
+ }) })] })), _jsxs(Typography, { paragraph: true, children: ["Cannot find your image? Reach out to support:", ' ', _jsxs("a", { href: 'https://postgres.ai/contact', target: "_blank", className: styles.externalLink, children: ["https://postgres.ai/contact", _jsx(ExternalIcon, { className: styles.externalIcon })] })] })] })] }), _jsxs(Box, { mb: 3, children: [_jsx(ConfigSectionTitle, { tag: "databaseConfigs" }), _jsx("span", { className: classes.grayText, style: { marginTop: '0.5rem', display: 'block' }, children: "Default PostgreSQL configuration used for all PostgreSQL instances running in containers managed by DBLab." }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_buffers parameter", value: formik.values.sharedBuffers, tooltipText: tooltipText.sharedBuffers, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedBuffers', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_preload_libraries", value: formik.values.sharedPreloadLibraries, tooltipText: tooltipText.sharedPreloadLibraries, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedPreloadLibraries', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Query tuning parameters", value: typeof formik.values.tuningParams === 'object'
590
+ ? Object.entries(formik.values.tuningParams)
591
+ .map(([key, value]) => `${key}=${value}`)
592
+ .join('\n')
593
+ : formik.values.tuningParams, tooltipText: tooltipText.tuningParams, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('tuningParams', e.target.value) }), _jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
594
+ onTestConnectionClick({
595
+ type: 'fetchTuning',
596
+ });
597
+ }, disabled: testConnectionState.fetchTuning.loading ||
598
+ isConfigurationDisabled, children: ["Get from source database", testConnectionState.fetchTuning.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.fetchTuning.message.status === 'error' ||
599
+ testConnectionState.fetchTuning.error ? (_jsx(ResponseMessage, { type: testConnectionState.fetchTuning.error ||
600
+ testConnectionState.fetchTuning.message
601
+ ? 'error'
602
+ : '', message: testConnectionState.fetchTuning.error ||
603
+ testConnectionState.fetchTuning.message.message })) : null] }), formik.values.retrievalMode === 'logical' && (_jsxs(Box, { children: [_jsxs(Box, { children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalRestore\"" }), _jsx("span", { className: classes.grayText, children: "Restoring options." })] }), _jsx(InputWithTooltip, { label: "pg_restore jobs", value: formik.values.restoreParallelJobs, tooltipText: tooltipText.restoreParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgRestoreCustomOptions, label: "pg_restore customOptions", id: "pgRestoreCustomOptions", tooltipText: tooltipText.pgRestoreCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgRestoreCustomOptions', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Restore PostgreSQL configs", value: formik.values.restoreConfigs, tooltipText: tooltipText.restoreConfigs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreConfigs', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "restoreIgnoreErrors", checked: formik.values.restoreIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreIgnoreErrors', e.target.checked), classes: {
604
+ root: classes.checkboxRoot,
605
+ } }), label: 'Ignore errors during logical data restore' })] })), _jsx(Box, { mt: 1, children: _jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.refresh\"" }) }), _jsxs("span", { className: classes.grayText, children: ["Define full data refresh on schedule. The process requires at least one additional filesystem mount point. The schedule is to be specified using", ' ', _jsxs("a", { href: "https://en.wikipedia.org/wiki/Cron#Overview", target: "_blank", className: styles.externalLink, children: ["crontab format", _jsx(ExternalIcon, { className: styles.externalIcon })] }), "."] }), _jsx(InputWithTooltip, { label: "timetable", value: formik.values.timetable, tooltipText: tooltipText.timetable, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('timetable', e.target.value) })] }), _jsxs(Box, { mt: 2, mb: 2, sx: {
606
+ display: 'flex',
607
+ alignItems: 'center',
608
+ }, children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: () => {
609
+ formik.submitForm().then(() => {
610
+ scrollToField();
567
611
  });
568
- }, disabled: testConnectionState.fetchTuning.loading ||
569
- isConfigurationDisabled, children: ["Get from source database", testConnectionState.fetchTuning.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.fetchTuning.message.status === 'error' ||
570
- testConnectionState.fetchTuning.error ? (_jsx(ResponseMessage, { type: testConnectionState.fetchTuning.error ||
571
- testConnectionState.fetchTuning.message
572
- ? 'error'
573
- : '', message: testConnectionState.fetchTuning.error ||
574
- testConnectionState.fetchTuning.message.message })) : null] }), _jsxs(Box, { children: [_jsxs(Box, { children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalRestore\"" }), _jsx("span", { className: classes.grayText, children: "Restoring options." })] }), _jsx(InputWithTooltip, { label: "pg_restore jobs", value: formik.values.restoreParallelJobs, tooltipText: tooltipText.restoreParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgRestoreCustomOptions, label: "pg_restore customOptions", id: "pgRestoreCustomOptions", tooltipText: tooltipText.pgRestoreCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgRestoreCustomOptions', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Restore PostgreSQL configs", value: formik.values.restoreConfigs, tooltipText: tooltipText.restoreConfigs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreConfigs', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "restoreIgnoreErrors", checked: formik.values.restoreIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreIgnoreErrors', e.target.checked), classes: {
575
- root: classes.checkboxRoot,
576
- } }), label: 'Ignore errors during logical data restore' })] }), _jsx(Box, { mt: 1, children: _jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.refresh\"" }) }), _jsxs("span", { className: classes.grayText, children: ["Define full data refresh on schedule. The process requires at least one additional filesystem mount point. The schedule is to be specified using", ' ', _jsxs("a", { href: "https://en.wikipedia.org/wiki/Cron#Overview", target: "_blank", className: styles.externalLink, children: ["crontab format", _jsx(ExternalIcon, { className: styles.externalIcon })] }), "."] }), _jsx(InputWithTooltip, { label: "timetable", value: formik.values.timetable, tooltipText: tooltipText.timetable, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('timetable', e.target.value) })] }), _jsxs(Box, { mt: 2, mb: 2, sx: {
577
- display: 'flex',
578
- alignItems: 'center',
579
- }, children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: () => {
580
- formik.submitForm().then(() => {
581
- scrollToField();
582
- });
583
- }, disabled: formik.isSubmitting || isConfigurationDisabled, children: ["Apply changes", formik.isSubmitting && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), _jsx(Box, { sx: { px: 2 }, children: _jsx(Button, { variant: "outlined", color: "secondary", onClick: switchTab, children: "Cancel" }) })] }), (submitState.status && submitState.response) || configError ? (_jsx(ResponseMessage, { type: configError ? 'error' : submitState.status, message: configError || submitState.response })) : null] })), _jsx(Modal, { title: _jsx(ModalTitle, {}), onClose: () => setIsModalOpen(false), isOpen: isModalOpen, size: "xl", children: _jsx(Editor, { height: "70vh", width: "100%", defaultLanguage: "yaml", value: getFullConfigError ? getFullConfigError : fullConfig, loading: _jsx(StubSpinner, {}), theme: "vs-light", options: { domReadOnly: true, readOnly: true } }) })] }));
612
+ }, disabled: formik.isSubmitting || isConfigurationDisabled, children: ["Apply changes", formik.isSubmitting && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), _jsx(Box, { sx: { px: 2 }, children: _jsx(Button, { variant: "outlined", color: "secondary", onClick: switchTab, children: "Cancel" }) })] }), (submitState.status && submitState.response) || configError ? (_jsx(ResponseMessage, { type: configError ? 'error' : submitState.status, message: configError || submitState.response })) : null] })) : null] })), _jsx(Modal, { title: _jsx(ModalTitle, {}), onClose: () => setIsModalOpen(false), isOpen: isModalOpen, size: "xl", children: _jsx(Editor, { height: "70vh", width: "100%", defaultLanguage: "yaml", value: getFullConfigError ? getFullConfigError : fullConfig, loading: _jsx(StubSpinner, {}), theme: "vs-light", options: { domReadOnly: true, readOnly: true } }) })] }));
584
613
  });
@@ -1,4 +1,10 @@
1
1
  /// <reference types="react" />
2
+ export declare type RetrievalMode = 'logical' | 'physical';
3
+ export declare type PhysicalTool = 'walg' | 'pgbackrest' | 'customTool' | '';
4
+ export declare type PhysicalEnv = {
5
+ key: string;
6
+ value: string;
7
+ };
2
8
  export declare type FormValues = {
3
9
  debug: boolean;
4
10
  dockerImage: string;
@@ -22,6 +28,14 @@ export declare type FormValues = {
22
28
  restoreConfigs: string;
23
29
  pgDumpCustomOptions: string;
24
30
  pgRestoreCustomOptions: string;
31
+ retrievalMode: RetrievalMode;
32
+ physicalTool: PhysicalTool;
33
+ physicalDockerImage: string;
34
+ physicalSyncEnabled: boolean;
35
+ physicalWalgBackupName: string;
36
+ physicalPgbackrestStanza: string;
37
+ physicalPgbackrestDelta: boolean;
38
+ physicalEnvs: PhysicalEnv[];
25
39
  };
26
40
  export declare const useForm: (onSubmit: (values: FormValues) => void) => {
27
41
  formik: {
@@ -80,4 +94,10 @@ export declare const useForm: (onSubmit: (values: FormValues) => void) => {
80
94
  dbname: string;
81
95
  };
82
96
  isConnectionDataValid: string;
97
+ connectionString: string;
98
+ connectionStringError: string | null;
99
+ onConnectionStringChange: (s: string) => void;
100
+ markPortInitialState: (wasUnset: boolean) => void;
101
+ markPortDirty: () => void;
102
+ omitPortOnSubmit: boolean;
83
103
  }[];
@@ -4,14 +4,64 @@
4
4
  * Unauthorized copying of this file, via any medium is strictly prohibited
5
5
  *--------------------------------------------------------------------------
6
6
  */
7
+ import { useMemo, useState } from 'react';
7
8
  import { useFormik } from 'formik';
8
9
  import * as Yup from 'yup';
10
+ import { ConnectionStringError, connectionStringFromFields, connectionStringToFields, } from './connectionString';
11
+ // Logical-mode required fields. Validation skips the source-connection block
12
+ // when retrievalMode is "physical" — that branch uses tool-specific inputs
13
+ // instead of a connection URL.
9
14
  const Schema = Yup.object().shape({
10
15
  dockerImage: Yup.string().required('Docker image is required'),
11
- dbname: Yup.string().required('Dbname is required'),
12
- host: Yup.string().required('Host is required'),
13
- port: Yup.string().required('Port is required'),
14
- username: Yup.string().required('Username is required'),
16
+ dbname: Yup.string().when('retrievalMode', {
17
+ is: 'logical',
18
+ then: (s) => s.required('Dbname is required'),
19
+ }),
20
+ host: Yup.string().when('retrievalMode', {
21
+ is: 'logical',
22
+ then: (s) => s.required('Host is required'),
23
+ }),
24
+ port: Yup.string().when('retrievalMode', {
25
+ is: 'logical',
26
+ then: (s) => s.required('Port is required'),
27
+ }),
28
+ username: Yup.string().when('retrievalMode', {
29
+ is: 'logical',
30
+ then: (s) => s.required('Username is required'),
31
+ }),
32
+ physicalEnvs: Yup.array()
33
+ .of(Yup.object().shape({
34
+ key: Yup.string(),
35
+ value: Yup.string(),
36
+ }))
37
+ .test('unique-keys', '', function (envs) {
38
+ if (!envs)
39
+ return true;
40
+ const positions = new Map();
41
+ envs.forEach((row, i) => {
42
+ var _a, _b;
43
+ const trimmed = ((_a = row === null || row === void 0 ? void 0 : row.key) !== null && _a !== void 0 ? _a : '').trim();
44
+ if (!trimmed)
45
+ return;
46
+ const list = (_b = positions.get(trimmed)) !== null && _b !== void 0 ? _b : [];
47
+ list.push(i);
48
+ positions.set(trimmed, list);
49
+ });
50
+ const dupes = [];
51
+ for (const indices of positions.values()) {
52
+ if (indices.length < 2)
53
+ continue;
54
+ for (const i of indices) {
55
+ dupes.push(this.createError({
56
+ path: `${this.path}[${i}].key`,
57
+ message: 'Duplicate key',
58
+ }));
59
+ }
60
+ }
61
+ if (dupes.length === 0)
62
+ return true;
63
+ return new Yup.ValidationError(dupes);
64
+ }),
15
65
  });
16
66
  export const useForm = (onSubmit) => {
17
67
  const formik = useFormik({
@@ -38,12 +88,71 @@ export const useForm = (onSubmit) => {
38
88
  pgRestoreCustomOptions: '',
39
89
  dumpIgnoreErrors: false,
40
90
  restoreIgnoreErrors: false,
91
+ retrievalMode: 'logical',
92
+ physicalTool: '',
93
+ physicalDockerImage: '',
94
+ physicalSyncEnabled: false,
95
+ physicalWalgBackupName: '',
96
+ physicalPgbackrestStanza: '',
97
+ physicalPgbackrestDelta: false,
98
+ physicalEnvs: [],
41
99
  },
42
100
  validationSchema: Schema,
43
101
  onSubmit,
44
102
  validateOnBlur: false,
45
103
  validateOnChange: false,
46
104
  });
105
+ const [originalPortWasUnset, setOriginalPortWasUnset] = useState(false);
106
+ const [portDirty, setPortDirty] = useState(false);
107
+ const [connectionStringError, setConnectionStringError] = useState(null);
108
+ const connectionString = useMemo(() => connectionStringFromFields({
109
+ host: formik.values.host,
110
+ port: formik.values.port || '5432',
111
+ username: formik.values.username,
112
+ dbname: formik.values.dbname,
113
+ }, { omitDefaultPort: originalPortWasUnset && !portDirty }), [
114
+ formik.values.host,
115
+ formik.values.port,
116
+ formik.values.username,
117
+ formik.values.dbname,
118
+ originalPortWasUnset,
119
+ portDirty,
120
+ ]);
121
+ const onConnectionStringChange = (s) => {
122
+ if (!s.trim()) {
123
+ setConnectionStringError(null);
124
+ formik.setValues({
125
+ ...formik.values,
126
+ host: '',
127
+ port: '',
128
+ username: '',
129
+ dbname: '',
130
+ });
131
+ return;
132
+ }
133
+ try {
134
+ const parsed = connectionStringToFields(s);
135
+ setConnectionStringError(null);
136
+ if (parsed.portWasExplicit)
137
+ setPortDirty(true);
138
+ formik.setValues({
139
+ ...formik.values,
140
+ host: parsed.fields.host,
141
+ port: parsed.fields.port,
142
+ username: parsed.fields.username,
143
+ dbname: parsed.fields.dbname,
144
+ });
145
+ }
146
+ catch (err) {
147
+ if (err instanceof ConnectionStringError)
148
+ setConnectionStringError(err.message);
149
+ else
150
+ throw err;
151
+ }
152
+ };
153
+ const markPortInitialState = (wasUnset) => setOriginalPortWasUnset(wasUnset);
154
+ const markPortDirty = () => setPortDirty(true);
155
+ const omitPortOnSubmit = originalPortWasUnset && !portDirty;
47
156
  const formatDatabaseArray = (database) => {
48
157
  let databases = [];
49
158
  const splitDatabaseArray = database.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
@@ -71,5 +180,17 @@ export const useForm = (onSubmit) => {
71
180
  formik.values.port &&
72
181
  formik.values.username &&
73
182
  formik.values.dbname;
74
- return [{ formik, connectionData, isConnectionDataValid }];
183
+ return [
184
+ {
185
+ formik,
186
+ connectionData,
187
+ isConnectionDataValid,
188
+ connectionString,
189
+ connectionStringError,
190
+ onConnectionStringChange,
191
+ markPortInitialState,
192
+ markPortDirty,
193
+ omitPortOnSubmit,
194
+ },
195
+ ];
75
196
  };
@@ -1,22 +1,6 @@
1
1
  import { dockerImageOptions } from '../configOptions';
2
+ import { dockerImagesConfig, genericImagePrefix } from '../dockerCatalog';
2
3
  const seContainerRegistry = 'se-images';
3
- const genericImagePrefix = 'postgresai/extended-postgres';
4
- // Predefined list of Docker images for UI display
5
- // This list is shown to users for convenient selection
6
- // IMPORTANT: if user specified an image in config that's not in this list,
7
- // it will be automatically added via createEnhancedDockerImages()
8
- const dockerImagesConfig = {
9
- '9.6': ['0.5.3', '0.5.2', '0.5.1'],
10
- '10': ['0.5.3', '0.5.2', '0.5.1'],
11
- '11': ['0.5.3', '0.5.2', '0.5.1'],
12
- '12': ['0.5.3', '0.5.2', '0.5.1'],
13
- '13': ['0.5.3', '0.5.2', '0.5.1'],
14
- '14': ['0.5.3', '0.5.2', '0.5.1'],
15
- '15': ['0.5.3', '0.5.2', '0.5.1'],
16
- '16': ['0.5.3', '0.5.2', '0.5.1'],
17
- '17': ['0.5.3', '0.5.2', '0.5.1'],
18
- '18': ['0.6.1'],
19
- };
20
4
  export const uniqueChipValue = (values) => {
21
5
  const splitChipArray = values.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
22
6
  let databaseArray = [];
@@ -67,7 +67,6 @@ export const Instance = observer((props) => {
67
67
  };
68
68
  const isInstanceIntegrated = instanceRetrieval ||
69
69
  (!isLoadingInstance && !isLoadingInstanceRetrieval && instance && (instance === null || instance === void 0 ? void 0 : instance.url) && !instanceError);
70
- const isConfigurationActive = (instanceRetrieval === null || instanceRetrieval === void 0 ? void 0 : instanceRetrieval.mode) !== 'physical';
71
70
  useEffect(() => {
72
71
  load(instanceId, isPlatform);
73
72
  }, [instanceId]);
@@ -79,14 +78,13 @@ export const Instance = observer((props) => {
79
78
  var _a, _b;
80
79
  if (instance &&
81
80
  ((_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.retrieving) === null || _b === void 0 ? void 0 : _b.status) === 'pending' &&
82
- isConfigurationActive &&
83
81
  !hasBeenRedirected) {
84
82
  setActiveTab(TABS_INDEX.CONFIGURATION);
85
83
  setHasBeenRedirected(true);
86
84
  }
87
85
  }, [instance, hasBeenRedirected]);
88
86
  return (_jsx(HostProvider, { value: props, children: _jsxs(StoresProvider, { value: stores, children: [props.elements.breadcrumbs, _jsx(SectionTitle, { text: props.title, level: 1, tag: "h1", className: classes.title, rightContent: _jsx(Button, { onClick: () => load(props.instanceId, isPlatform), isDisabled: !instance && !instanceError, className: classes.reloadButton, children: "Reload info" }), children: isInstanceIntegrated && (_jsx(InstanceTabs, { instanceId: props.instanceId, tab: activeTab, onTabChange: (tabID) => setActiveTab(tabID), isPlatform: isPlatform, hasLogs: api.initWS !== undefined, hideInstanceTabs: props.hideBranchingFeatures })) }), instanceError && (_jsx(ErrorStub, { ...instanceError, className: classes.errorStub })), isInstanceIntegrated ? (_jsxs(_Fragment, { children: [_jsxs(TabPanel, { value: activeTab, index: TABS_INDEX.OVERVIEW, children: [!instanceError && (_jsx("div", { className: classes.content, children: instance && ((_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.retrieving) === null || _b === void 0 ? void 0 : _b.status) ? (_jsxs(_Fragment, { children: [_jsx(Clones, {}), _jsx(Info, { hideBranchingFeatures: props.hideBranchingFeatures })] })) : (_jsx(StubSpinner, {})) })), _jsx(ClonesModal, {}), _jsx(SnapshotsModal, {})] }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CLONES, children: activeTab === TABS_INDEX.CLONES && (_jsx("div", { className: classes.content, children: !instanceError &&
89
- (instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, isConfigurationActive: isConfigurationActive, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org }) })) : (!instanceError && (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx("div", { className: classes.content, children: _jsx(StubSpinner, {}) }) })))] }) }));
87
+ (instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org }) })) : (!instanceError && (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx("div", { className: classes.content, children: _jsx(StubSpinner, {}) }) })))] }) }));
90
88
  });
91
89
  function TabPanel(props) {
92
90
  const { children, value, index, ...other } = props;
@@ -5,6 +5,7 @@ import { Config } from '@postgres.ai/shared/types/api/entities/config';
5
5
  import { GetConfig } from '@postgres.ai/shared/types/api/endpoints/getConfig';
6
6
  import { UpdateConfig } from '@postgres.ai/shared/types/api/endpoints/updateConfig';
7
7
  import { TestDbSource } from '@postgres.ai/shared/types/api/endpoints/testDbSource';
8
+ import { ProbeSource } from '@postgres.ai/shared/types/api/endpoints/probeSource';
8
9
  import { RefreshInstance } from '@postgres.ai/shared/types/api/endpoints/refreshInstance';
9
10
  import { DestroyClone } from '@postgres.ai/shared/types/api/endpoints/destroyClone';
10
11
  import { ResetClone } from '@postgres.ai/shared/types/api/endpoints/resetClone';
@@ -35,6 +36,7 @@ export declare type Api = {
35
36
  getConfig?: GetConfig;
36
37
  updateConfig?: UpdateConfig;
37
38
  testDbSource?: TestDbSource;
39
+ probeSource?: ProbeSource;
38
40
  getFullConfig?: GetFullConfig;
39
41
  getSeImages?: GetSeImages;
40
42
  getEngine?: GetEngine;
@@ -104,6 +106,17 @@ export declare class MainStore {
104
106
  dockerTag?: string | undefined;
105
107
  dockerImageType?: string | undefined;
106
108
  debug: boolean | undefined;
109
+ retrievalMode: import("@postgres.ai/shared/types/api/entities/config").RetrievalMode;
110
+ physicalTool: string;
111
+ physicalDockerImage: string;
112
+ physicalSyncEnabled: boolean;
113
+ physicalWalgBackupName: string;
114
+ physicalPgbackrestStanza: string;
115
+ physicalPgbackrestDelta: boolean;
116
+ physicalEnvs: {
117
+ key: string;
118
+ value: string;
119
+ }[];
107
120
  dockerImage: string | undefined;
108
121
  } | null | undefined>;
109
122
  updateConfig: (values: Config, instanceId: string) => Promise<Response | null | undefined>;
@@ -127,6 +140,13 @@ export declare class MainStore {
127
140
  message: string;
128
141
  };
129
142
  } | undefined>;
143
+ probeSource: (values: {
144
+ url: string;
145
+ password: string;
146
+ }) => Promise<{
147
+ response: import("@postgres.ai/shared/types/api/endpoints/probeSource").ProposedConfig | null;
148
+ error: import("@postgres.ai/shared/types/api/endpoints/probeSource").ProbeSourceError | null;
149
+ } | undefined>;
130
150
  resetClone: (cloneId: string, snapshotId: string) => Promise<boolean | undefined>;
131
151
  destroyClone: (cloneId: string) => Promise<boolean | undefined>;
132
152
  private liveUpdateInstance;