@postgres.ai/shared 4.1.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/.gitlab-ci.yml +7 -3
  2. package/components/UpgradeCloneModal/index.tsx +145 -0
  3. package/dist/components/UpgradeCloneModal/index.d.ts +11 -0
  4. package/dist/components/UpgradeCloneModal/index.js +75 -0
  5. package/dist/package.json +2 -2
  6. package/dist/pages/Branches/Branch/index.js +27 -2
  7. package/dist/pages/Branches/Branch/stores/Main.d.ts +5 -0
  8. package/dist/pages/Branches/Branch/stores/Main.js +29 -0
  9. package/dist/pages/Branches/components/BranchesTable/index.js +2 -2
  10. package/dist/pages/Branches/index.js +10 -1
  11. package/dist/pages/Clone/Status/index.js +3 -0
  12. package/dist/pages/Clone/index.js +12 -4
  13. package/dist/pages/Clone/stores/Main.d.ts +9 -1
  14. package/dist/pages/Clone/stores/Main.js +40 -3
  15. package/dist/pages/CreateBranch/stores/Main.d.ts +3 -0
  16. package/dist/pages/CreateClone/index.js +2 -0
  17. package/dist/pages/CreateClone/stores/Main.d.ts +5 -1
  18. package/dist/pages/CreateClone/stores/Main.js +8 -3
  19. package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
  20. package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
  21. package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
  22. package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
  23. package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
  24. package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
  25. package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
  26. package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
  27. package/dist/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
  28. package/dist/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
  29. package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +13 -0
  30. package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
  31. package/dist/pages/Instance/Configuration/SimpleMode/index.d.ts +19 -0
  32. package/dist/pages/Instance/Configuration/SimpleMode/index.js +166 -0
  33. package/dist/pages/Instance/Configuration/configMode.d.ts +2 -0
  34. package/dist/pages/Instance/Configuration/configMode.js +7 -0
  35. package/dist/pages/Instance/Configuration/configOptions.d.ts +16 -0
  36. package/dist/pages/Instance/Configuration/configOptions.js +71 -0
  37. package/dist/pages/Instance/Configuration/connectionString.d.ts +20 -0
  38. package/dist/pages/Instance/Configuration/connectionString.js +129 -0
  39. package/dist/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
  40. package/dist/pages/Instance/Configuration/dockerCatalog.js +19 -0
  41. package/dist/pages/Instance/Configuration/index.d.ts +1 -2
  42. package/dist/pages/Instance/Configuration/index.js +115 -86
  43. package/dist/pages/Instance/Configuration/useForm.d.ts +20 -0
  44. package/dist/pages/Instance/Configuration/useForm.js +126 -5
  45. package/dist/pages/Instance/Configuration/utils/index.js +1 -17
  46. package/dist/pages/Instance/Info/Snapshots/Calendar/Day/index.js +3 -3
  47. package/dist/pages/Instance/Info/Snapshots/Calendar/utils.d.ts +3 -0
  48. package/dist/pages/Instance/Info/Snapshots/utils.d.ts +6 -0
  49. package/dist/pages/Instance/Snapshots/components/SnapshotsList/index.js +11 -1
  50. package/dist/pages/Instance/Snapshots/index.js +11 -3
  51. package/dist/pages/Instance/Snapshots/utils/index.d.ts +3 -0
  52. package/dist/pages/Instance/Tabs/styles.js +6 -6
  53. package/dist/pages/Instance/index.js +1 -3
  54. package/dist/pages/Instance/stores/Main.d.ts +22 -2
  55. package/dist/pages/Instance/stores/Main.js +26 -9
  56. package/dist/pages/Snapshots/Snapshot/index.js +28 -2
  57. package/dist/pages/Snapshots/Snapshot/stores/Main.d.ts +5 -0
  58. package/dist/pages/Snapshots/Snapshot/stores/Main.js +29 -0
  59. package/dist/stores/Snapshots.d.ts +1 -1
  60. package/dist/stores/Snapshots.js +12 -6
  61. package/dist/types/api/endpoints/getBranches.d.ts +6 -0
  62. package/dist/types/api/endpoints/probeSource.d.ts +32 -0
  63. package/dist/types/api/endpoints/probeSource.js +1 -0
  64. package/dist/types/api/endpoints/testDbSource.d.ts +3 -1
  65. package/dist/types/api/endpoints/testDbSource.js +6 -0
  66. package/dist/types/api/endpoints/updateBranch.d.ts +11 -0
  67. package/dist/types/api/endpoints/updateBranch.js +1 -0
  68. package/dist/types/api/endpoints/updateSnapshot.d.ts +11 -0
  69. package/dist/types/api/endpoints/updateSnapshot.js +1 -0
  70. package/dist/types/api/endpoints/upgradeClone.d.ts +8 -0
  71. package/dist/types/api/endpoints/upgradeClone.js +1 -0
  72. package/dist/types/api/entities/branchSnapshots.d.ts +3 -0
  73. package/dist/types/api/entities/clone.d.ts +9 -2
  74. package/dist/types/api/entities/config.d.ts +32 -0
  75. package/dist/types/api/entities/config.js +45 -18
  76. package/dist/types/api/entities/instance.d.ts +11 -1
  77. package/dist/types/api/entities/instanceState.d.ts +16 -1
  78. package/dist/types/api/entities/snapshot.d.ts +6 -0
  79. package/dist/utils/api.d.ts +1 -1
  80. package/dist/utils/api.js +6 -0
  81. package/dist/utils/clone.d.ts +3 -1
  82. package/dist/utils/clone.js +7 -1
  83. package/dist/utils/clonePoller.d.ts +12 -0
  84. package/dist/utils/clonePoller.js +41 -0
  85. package/dist/utils/date.js +12 -12
  86. package/dist/utils/protection.d.ts +5 -0
  87. package/dist/utils/protection.js +23 -0
  88. package/package.json +2 -2
  89. package/pages/Branches/Branch/index.tsx +70 -0
  90. package/pages/Branches/Branch/stores/Main.ts +40 -0
  91. package/pages/Branches/components/BranchesTable/index.tsx +2 -0
  92. package/pages/Branches/index.tsx +13 -1
  93. package/pages/Clone/Status/index.tsx +3 -0
  94. package/pages/Clone/index.tsx +58 -0
  95. package/pages/Clone/stores/Main.ts +51 -8
  96. package/pages/CreateClone/index.tsx +3 -0
  97. package/pages/CreateClone/stores/Main.ts +10 -8
  98. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.tsx +129 -0
  99. package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.tsx +89 -0
  100. package/pages/Instance/Configuration/PhysicalMode/Sync/index.tsx +64 -0
  101. package/pages/Instance/Configuration/PhysicalMode/Walg/index.tsx +63 -0
  102. package/pages/Instance/Configuration/PhysicalMode/index.tsx +93 -0
  103. package/pages/Instance/Configuration/SimpleMode/PreviewCard.tsx +176 -0
  104. package/pages/Instance/Configuration/SimpleMode/index.tsx +303 -0
  105. package/pages/Instance/Configuration/configMode.ts +12 -0
  106. package/pages/Instance/Configuration/configOptions.ts +111 -0
  107. package/pages/Instance/Configuration/connectionString.ts +157 -0
  108. package/pages/Instance/Configuration/dockerCatalog.ts +21 -0
  109. package/pages/Instance/Configuration/index.tsx +151 -49
  110. package/pages/Instance/Configuration/useForm.ts +155 -5
  111. package/pages/Instance/Configuration/utils/index.ts +1 -17
  112. package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +3 -3
  113. package/pages/Instance/Snapshots/components/SnapshotsList/index.tsx +21 -1
  114. package/pages/Instance/Snapshots/index.tsx +15 -5
  115. package/pages/Instance/Tabs/styles.ts +7 -7
  116. package/pages/Instance/index.tsx +0 -4
  117. package/pages/Instance/stores/Main.ts +29 -7
  118. package/pages/Snapshots/Snapshot/index.tsx +71 -0
  119. package/pages/Snapshots/Snapshot/stores/Main.ts +40 -0
  120. package/postgres.ai-shared-4.2.0.tgz +0 -0
  121. package/stores/Snapshots.ts +16 -7
  122. package/types/api/endpoints/getBranches.ts +3 -0
  123. package/types/api/endpoints/probeSource.ts +34 -0
  124. package/types/api/endpoints/testDbSource.ts +10 -1
  125. package/types/api/endpoints/updateBranch.ts +11 -0
  126. package/types/api/endpoints/updateSnapshot.ts +11 -0
  127. package/types/api/endpoints/upgradeClone.ts +8 -0
  128. package/types/api/entities/clone.ts +13 -1
  129. package/types/api/entities/config.ts +46 -0
  130. package/types/api/entities/instanceState.ts +6 -0
  131. package/types/api/entities/snapshot.ts +3 -0
  132. package/utils/api.ts +8 -0
  133. package/utils/clone.ts +7 -1
  134. package/utils/clonePoller.ts +47 -0
  135. package/utils/date.ts +19 -14
  136. package/utils/protection.ts +25 -0
  137. package/postgres.ai-shared-4.1.2.tgz +0 -0
@@ -1,3 +1,74 @@
1
+ import { dockerImagesConfig, genericImagePrefix } from './dockerCatalog';
2
+ // Mapping from the engine probe's provider key (probe.Provider) to a
3
+ // dockerImageOptions.type value the form already understands. Keys must
4
+ // match the values defined in engine/internal/retrieval/probe/provider.go
5
+ // (ProviderRDS, ProviderAurora, etc.) — see Task 18 in the plan.
6
+ const providerKeyToImageType = {
7
+ generic: 'Generic Postgres',
8
+ rds: 'rds',
9
+ aurora: 'aurora',
10
+ cloudsql: 'google-cloud-sql',
11
+ supabase: 'supabase',
12
+ heroku: 'heroku',
13
+ timescale: 'timescale-cloud',
14
+ };
15
+ // Reads the most recent tag for a given PG major from the shared catalog.
16
+ // SE images (rds, aurora, etc.) require the platform-only getSeImages call
17
+ // and have no entry here — they return defaultTag: undefined.
18
+ const genericDefaultTag = (pgMajorVersion) => {
19
+ if (!pgMajorVersion)
20
+ return undefined;
21
+ const version = String(pgMajorVersion);
22
+ const tags = dockerImagesConfig[version];
23
+ if (!tags || tags.length === 0)
24
+ return undefined;
25
+ return `${version}-${tags[0]}`;
26
+ };
27
+ // genericImagePathForVersion returns a pullable generic extended-postgres
28
+ // reference for a PG major version, or '' when the version is unknown. Simple
29
+ // mode uses it as the CE fallback for managed providers, whose SE images are
30
+ // only resolvable through the platform-only getSeImages call.
31
+ export const genericImagePathForVersion = (pgMajorVersion) => {
32
+ const tag = genericDefaultTag(pgMajorVersion);
33
+ return tag ? `${genericImagePrefix}:${tag}` : '';
34
+ };
35
+ // selectSeImage picks the SE catalog entry matching the detected major version.
36
+ // Returns undefined when the catalog is empty (CE) or has no matching version,
37
+ // which signals the caller to fall back to the generic image.
38
+ export const selectSeImage = (seImages, pgMajorVersion) => seImages === null || seImages === void 0 ? void 0 : seImages.find((image) => image.pg_major_version === String(pgMajorVersion));
39
+ // resolveDockerImagePath returns the concrete image reference Simple mode ships
40
+ // for a probed source. Managed providers fall back to the generic image in CE,
41
+ // so both the Apply payload and the preview show the same value.
42
+ export const resolveDockerImagePath = (providerKey, pgMajorVersion, dockerTag) => {
43
+ const mapping = providerKeyToImage(providerKey, pgMajorVersion);
44
+ const tag = dockerTag || mapping.defaultTag || '';
45
+ if (mapping.imageType === 'Generic Postgres') {
46
+ return tag ? `${genericImagePrefix}:${tag}` : '';
47
+ }
48
+ return genericImagePathForVersion(pgMajorVersion);
49
+ };
50
+ // Resolves a probe provider key to a concrete docker image type the
51
+ // Configuration form can write into the projection. Unknown keys (including
52
+ // "azure", which has no matching SE image today) fall back to the generic
53
+ // Postgres image and set fallback=true so the UI can warn.
54
+ export const providerKeyToImage = (providerKey, pgMajorVersion) => {
55
+ const known = providerKeyToImageType[providerKey];
56
+ if (!known) {
57
+ return {
58
+ imageType: 'Generic Postgres',
59
+ defaultTag: genericDefaultTag(pgMajorVersion),
60
+ fallback: true,
61
+ };
62
+ }
63
+ if (known === 'Generic Postgres') {
64
+ return {
65
+ imageType: 'Generic Postgres',
66
+ defaultTag: genericDefaultTag(pgMajorVersion),
67
+ fallback: false,
68
+ };
69
+ }
70
+ return { imageType: known, defaultTag: undefined, fallback: false };
71
+ };
1
72
  export const dockerImageOptions = [
2
73
  {
3
74
  name: 'Generic PostgreSQL (postgresai/extended-postgres)',
@@ -0,0 +1,20 @@
1
+ export declare type ConnectionFields = {
2
+ host: string;
3
+ port: string;
4
+ username: string;
5
+ dbname: string;
6
+ };
7
+ export declare type ParseResult = {
8
+ fields: ConnectionFields;
9
+ portWasExplicit: boolean;
10
+ };
11
+ export declare class ConnectionStringError extends Error {
12
+ }
13
+ export declare const ErrPasswordInConnectionString = "Password must be entered in the Password field, not the connection string.";
14
+ export declare const ErrMultiHost = "Multi-host connection strings are not supported.";
15
+ export declare const ErrInvalidConnectionString = "Could not parse the connection string.";
16
+ export declare const connectionStringToFields: (s: string) => ParseResult;
17
+ export declare type SerializeOptions = {
18
+ omitDefaultPort?: boolean;
19
+ };
20
+ export declare const connectionStringFromFields: (fields: ConnectionFields, opts?: SerializeOptions) => string;
@@ -0,0 +1,129 @@
1
+ // connectionString.ts — Expert-mode helper that converts between the
2
+ // (host, port, username, dbname) shape stored in `server.yml` and the
3
+ // single URL field the form exposes. The engine remains the source of
4
+ // truth for actual probing (see engine/internal/retrieval/probe/parser.go);
5
+ // this parser only powers the form's load/save round-trip.
6
+ //
7
+ // Accepts both URI form (postgres://user@host:5432/dbname) and DSN form
8
+ // (host=db port=5432 user=app dbname=shop). Rejects any input that carries
9
+ // a password — passwords belong in the masked field, never the URL field.
10
+ export class ConnectionStringError extends Error {
11
+ }
12
+ export const ErrPasswordInConnectionString = 'Password must be entered in the Password field, not the connection string.';
13
+ export const ErrMultiHost = 'Multi-host connection strings are not supported.';
14
+ export const ErrInvalidConnectionString = 'Could not parse the connection string.';
15
+ const URI_SCHEMES = ['postgres:', 'postgresql:'];
16
+ const isUri = (s) => URI_SCHEMES.some((p) => s.toLowerCase().startsWith(p));
17
+ const stripBrackets = (h) => (h.startsWith('[') && h.endsWith(']') ? h.slice(1, -1) : h);
18
+ const parseUri = (s) => {
19
+ let url;
20
+ try {
21
+ url = new URL(s);
22
+ }
23
+ catch {
24
+ throw new ConnectionStringError(ErrInvalidConnectionString);
25
+ }
26
+ if (url.password)
27
+ throw new ConnectionStringError(ErrPasswordInConnectionString);
28
+ if (!url.hostname)
29
+ throw new ConnectionStringError(ErrInvalidConnectionString);
30
+ if (url.hostname.includes(','))
31
+ throw new ConnectionStringError(ErrMultiHost);
32
+ const portWasExplicit = url.port !== '';
33
+ const path = url.pathname.startsWith('/') ? url.pathname.slice(1) : url.pathname;
34
+ const dbname = path.includes(',') ? '' : decodeURIComponent(path);
35
+ return {
36
+ fields: {
37
+ host: stripBrackets(url.hostname),
38
+ port: portWasExplicit ? url.port : '5432',
39
+ username: url.username ? decodeURIComponent(url.username) : '',
40
+ dbname,
41
+ },
42
+ portWasExplicit,
43
+ };
44
+ };
45
+ // Tokenizer for libpq DSN form: key=value pairs, values may be single-quoted
46
+ // when they contain spaces (libpq spec). Throws on malformed input.
47
+ const tokenizeDsn = (s) => {
48
+ const out = {};
49
+ let i = 0;
50
+ while (i < s.length) {
51
+ // skip leading whitespace
52
+ while (i < s.length && /\s/.test(s[i]))
53
+ i++;
54
+ if (i >= s.length)
55
+ break;
56
+ // read key
57
+ const keyStart = i;
58
+ while (i < s.length && s[i] !== '=' && !/\s/.test(s[i]))
59
+ i++;
60
+ if (i >= s.length || s[i] !== '=')
61
+ throw new ConnectionStringError(ErrInvalidConnectionString);
62
+ const key = s.slice(keyStart, i);
63
+ i++; // skip '='
64
+ // read value (possibly quoted)
65
+ let value = '';
66
+ if (s[i] === "'") {
67
+ i++;
68
+ while (i < s.length && s[i] !== "'") {
69
+ if (s[i] === '\\' && i + 1 < s.length) {
70
+ value += s[i + 1];
71
+ i += 2;
72
+ continue;
73
+ }
74
+ value += s[i];
75
+ i++;
76
+ }
77
+ if (i >= s.length)
78
+ throw new ConnectionStringError(ErrInvalidConnectionString);
79
+ i++; // skip closing quote
80
+ }
81
+ else {
82
+ while (i < s.length && !/\s/.test(s[i])) {
83
+ value += s[i];
84
+ i++;
85
+ }
86
+ }
87
+ out[key.toLowerCase()] = value;
88
+ }
89
+ return out;
90
+ };
91
+ const parseDsn = (s) => {
92
+ var _a, _b, _c, _d, _e, _f, _g;
93
+ const tokens = tokenizeDsn(s);
94
+ if (tokens.password)
95
+ throw new ConnectionStringError(ErrPasswordInConnectionString);
96
+ const host = (_b = (_a = tokens.host) !== null && _a !== void 0 ? _a : tokens.hostaddr) !== null && _b !== void 0 ? _b : '';
97
+ if (host.includes(','))
98
+ throw new ConnectionStringError(ErrMultiHost);
99
+ const portRaw = (_c = tokens.port) !== null && _c !== void 0 ? _c : '';
100
+ const portWasExplicit = portRaw !== '';
101
+ return {
102
+ fields: {
103
+ host,
104
+ port: portWasExplicit ? portRaw : '5432',
105
+ username: (_e = (_d = tokens.user) !== null && _d !== void 0 ? _d : tokens.username) !== null && _e !== void 0 ? _e : '',
106
+ dbname: (_g = (_f = tokens.dbname) !== null && _f !== void 0 ? _f : tokens.database) !== null && _g !== void 0 ? _g : '',
107
+ },
108
+ portWasExplicit,
109
+ };
110
+ };
111
+ export const connectionStringToFields = (s) => {
112
+ const trimmed = s.trim();
113
+ if (!trimmed)
114
+ throw new ConnectionStringError(ErrInvalidConnectionString);
115
+ if (isUri(trimmed))
116
+ return parseUri(trimmed);
117
+ if (trimmed.includes('='))
118
+ return parseDsn(trimmed);
119
+ throw new ConnectionStringError(ErrInvalidConnectionString);
120
+ };
121
+ // formatHost wraps IPv6 literals in brackets so the resulting URI parses cleanly.
122
+ const formatHost = (host) => (host.includes(':') ? `[${host}]` : host);
123
+ export const connectionStringFromFields = (fields, opts = {}) => {
124
+ const userPart = fields.username ? `${encodeURIComponent(fields.username)}@` : '';
125
+ const showPort = fields.port && !(opts.omitDefaultPort && fields.port === '5432');
126
+ const portPart = showPort ? `:${fields.port}` : '';
127
+ const dbPart = fields.dbname ? `/${encodeURIComponent(fields.dbname)}` : '';
128
+ return `postgres://${userPart}${formatHost(fields.host)}${portPart}${dbPart}`;
129
+ };
@@ -0,0 +1,2 @@
1
+ export declare const genericImagePrefix = "postgresai/extended-postgres";
2
+ export declare const dockerImagesConfig: Record<string, string[]>;
@@ -0,0 +1,19 @@
1
+ // Shared image-catalog constants. Kept in its own module so both
2
+ // `configOptions.ts` and `utils/index.ts` can read them without creating
3
+ // an import cycle (utils → configOptions today).
4
+ export const genericImagePrefix = 'postgresai/extended-postgres';
5
+ // Predefined Docker image catalog for the Generic Postgres image. If a
6
+ // user's config references a tag not listed here, createEnhancedDockerImages
7
+ // in utils/index.ts appends it at runtime.
8
+ export 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
+ };
@@ -1,9 +1,8 @@
1
1
  /// <reference types="react" />
2
- export declare const Configuration: (({ instanceId, switchActiveTab, reload, isConfigurationActive, disableConfigModification, }: {
2
+ export declare const Configuration: (({ instanceId, switchActiveTab, reload, disableConfigModification, }: {
3
3
  instanceId: string;
4
4
  switchActiveTab: (_: null, activeTab: number) => void;
5
5
  reload: () => void;
6
- isConfigurationActive: boolean;
7
6
  disableConfigModification?: boolean | undefined;
8
7
  }) => JSX.Element) & {
9
8
  displayName: string;
@@ -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
  }[];