@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.
- package/package.json +1 -1
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
- package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
- package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
- package/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
- package/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
- package/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
- package/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
- package/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
- package/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
- package/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +11 -0
- package/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
- package/pages/Instance/Configuration/SimpleMode/index.d.ts +14 -0
- package/pages/Instance/Configuration/SimpleMode/index.js +107 -0
- package/pages/Instance/Configuration/configMode.d.ts +2 -0
- package/pages/Instance/Configuration/configMode.js +7 -0
- package/pages/Instance/Configuration/configOptions.d.ts +6 -0
- package/pages/Instance/Configuration/configOptions.js +48 -0
- package/pages/Instance/Configuration/connectionString.d.ts +20 -0
- package/pages/Instance/Configuration/connectionString.js +129 -0
- package/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
- package/pages/Instance/Configuration/dockerCatalog.js +19 -0
- package/pages/Instance/Configuration/index.d.ts +1 -2
- package/pages/Instance/Configuration/index.js +115 -86
- package/pages/Instance/Configuration/useForm.d.ts +20 -0
- package/pages/Instance/Configuration/useForm.js +126 -5
- package/pages/Instance/Configuration/utils/index.js +1 -17
- package/pages/Instance/index.js +1 -3
- package/pages/Instance/stores/Main.d.ts +20 -0
- package/pages/Instance/stores/Main.js +9 -0
- package/types/api/endpoints/probeSource.d.ts +32 -0
- package/types/api/endpoints/probeSource.js +1 -0
- package/types/api/entities/config.d.ts +32 -0
- 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,
|
|
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 =
|
|
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
|
-
:
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}, disabled:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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: '
|
|
532
|
+
type: 'default',
|
|
538
533
|
});
|
|
539
|
-
}, disabled: testConnectionState.
|
|
540
|
-
isConfigurationDisabled, children: ["
|
|
541
|
-
|
|
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
|
-
:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
553
|
-
dockerPath:
|
|
551
|
+
dockerImage: e.target.value,
|
|
552
|
+
dockerPath: e.target.value,
|
|
554
553
|
});
|
|
555
|
-
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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:
|
|
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().
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 [
|
|
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 = [];
|
package/pages/Instance/index.js
CHANGED
|
@@ -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,
|
|
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;
|