@postgres.ai/shared 4.1.3 → 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.3.tgz +0 -0
@@ -7,8 +7,8 @@
7
7
  import { makeAutoObservable } from 'mobx';
8
8
  import { SnapshotsStore, } from '@postgres.ai/shared/stores/Snapshots';
9
9
  import { checkIsCloneStable } from '@postgres.ai/shared/utils/clone';
10
+ import { ClonePoller } from '@postgres.ai/shared/utils/clonePoller';
10
11
  import { getTextFromUnknownApiError } from '@postgres.ai/shared/utils/api';
11
- const UNSTABLE_CLONE_UPDATE_TIMEOUT = 1000;
12
12
  export class MainStore {
13
13
  constructor(api) {
14
14
  this.instance = null;
@@ -17,12 +17,16 @@ export class MainStore {
17
17
  this.cloneError = null;
18
18
  this.isResettingClone = false;
19
19
  this.resetCloneError = null;
20
+ this.isUpgradingClone = false;
21
+ this.upgradeCloneError = null;
20
22
  this.isDestroyingClone = false;
21
23
  this.destroyCloneError = null;
22
24
  this.isUpdatingClone = false;
23
25
  this.updateCloneError = null;
24
26
  this.isReloading = false;
27
+ this.poller = new ClonePoller();
25
28
  this.load = async (instanceId, cloneId) => {
29
+ this.poller.start();
26
30
  const [isInstanceOk, isCloneOk, isSnapshotsLoaded] = await Promise.all([
27
31
  this.loadInstance(instanceId),
28
32
  this.loadClone(instanceId, cloneId),
@@ -38,6 +42,7 @@ export class MainStore {
38
42
  this.isReloading = false;
39
43
  return isSuccess;
40
44
  };
45
+ this.stopPolling = () => this.poller.stop();
41
46
  this.loadInstance = async (instanceId) => {
42
47
  const { response, error } = await this.api.getInstance({
43
48
  instanceId,
@@ -59,12 +64,14 @@ export class MainStore {
59
64
  return Boolean(response);
60
65
  };
61
66
  this.loadClone = async (instanceId, cloneId) => {
62
- window.clearTimeout(this.cloneUpdateTimeout);
67
+ if (this.poller.isStopped)
68
+ return false;
69
+ this.poller.cancel();
63
70
  const { response, error } = await this.api.getClone({ instanceId, cloneId });
64
71
  if (response) {
65
72
  this.clone = response;
66
73
  if (!this.isCloneStable)
67
- this.cloneUpdateTimeout = window.setTimeout(() => this.loadClone(instanceId, cloneId), UNSTABLE_CLONE_UPDATE_TIMEOUT);
74
+ this.poller.scheduleNext(() => this.loadClone(instanceId, cloneId));
68
75
  }
69
76
  if (error) {
70
77
  if (error.status === 404) {
@@ -97,6 +104,23 @@ export class MainStore {
97
104
  this.isResettingClone = false;
98
105
  return Boolean(response);
99
106
  };
107
+ this.upgradeClone = async (dockerImage) => {
108
+ if (!this.instance || !this.clone || !this.api.upgradeClone)
109
+ return false;
110
+ this.isUpgradingClone = true;
111
+ this.upgradeCloneError = null;
112
+ const { response, error } = await this.api.upgradeClone({
113
+ instanceId: this.instance.id,
114
+ cloneId: this.clone.id,
115
+ dockerImage,
116
+ });
117
+ if (response)
118
+ await this.loadClone(this.instance.id, this.clone.id);
119
+ if (error)
120
+ this.upgradeCloneError = await getTextFromUnknownApiError(error);
121
+ this.isUpgradingClone = false;
122
+ return Boolean(response);
123
+ };
100
124
  this.destroyClone = async () => {
101
125
  if (!this.instance || !this.clone)
102
126
  return false;
@@ -139,6 +163,19 @@ export class MainStore {
139
163
  this.api = api;
140
164
  makeAutoObservable(this);
141
165
  }
166
+ // The closed Platform app implements the same Api and may not provide the endpoint, and the
167
+ // engine only accepts an upgrade once one is configured, so the action is hidden rather than
168
+ // offered and then refused.
169
+ get isUpgradeSupported() {
170
+ return Boolean(this.api.upgradeClone) && Boolean(this.upgradeTargetVersion);
171
+ }
172
+ // The major an upgrade lands on. It follows from the instance's upgrade image, so the UI reads
173
+ // it instead of asking for one.
174
+ get upgradeTargetVersion() {
175
+ var _a, _b;
176
+ const cloneUpgrade = (_b = (_a = this.instance) === null || _a === void 0 ? void 0 : _a.state) === null || _b === void 0 ? void 0 : _b.cloneUpgrade;
177
+ return (cloneUpgrade === null || cloneUpgrade === void 0 ? void 0 : cloneUpgrade.available) ? cloneUpgrade.targetVersion : undefined;
178
+ }
142
179
  get isCloneStable() {
143
180
  if (!this.clone)
144
181
  return false;
@@ -38,6 +38,9 @@ export declare class MainStore {
38
38
  logicalSize: number;
39
39
  message: string;
40
40
  branch: string;
41
+ protected: boolean;
42
+ protectedTill?: string | undefined;
43
+ deleteAt?: string | undefined;
41
44
  }[] | null | undefined>;
42
45
  }
43
46
  export {};
@@ -122,6 +122,8 @@ export const CreateClone = observer((props) => {
122
122
  return;
123
123
  history.push(props.routes.clone(stores.main.clone.id));
124
124
  }, [stores.main.clone, stores.main.isCloneStable]);
125
+ // Stop clone status polling on unmount.
126
+ useEffect(() => () => stores.main.stopPolling(), []);
125
127
  const headRendered = (_jsxs(_Fragment, { children: [_jsx("style", { children: 'p { margin: 0; }' }), props.elements.breadcrumbs, _jsx(SectionTitle, { tag: "h1", level: 1, text: "Create clone", className: styles.pageTitle, children: _jsx(InstanceTabs, { tab: TABS_INDEX.CLONES, isPlatform: props.isPlatform, instanceId: props.instanceId, hasLogs: props.api.initWS !== undefined, hideInstanceTabs: props.hideBranchingFeatures }) })] }));
126
128
  // Initial loading spinner.
127
129
  if (!stores.main.instance || isLoadingSnapshots)
@@ -23,7 +23,7 @@ export declare class MainStore {
23
23
  getSnapshotsError: Error | null;
24
24
  clone: Clone | null;
25
25
  cloneError: string | null;
26
- private cloneUpdateTimeout?;
26
+ private readonly poller;
27
27
  private readonly api;
28
28
  readonly snapshots?: SnapshotsStore;
29
29
  constructor(api: MainStoreApi);
@@ -44,6 +44,10 @@ export declare class MainStore {
44
44
  logicalSize: number;
45
45
  message: string;
46
46
  branch: string;
47
+ protected: boolean;
48
+ protectedTill?: string | undefined;
49
+ deleteAt?: string | undefined;
47
50
  }[] | null | undefined>;
51
+ stopPolling: () => void;
48
52
  private updateCloneUntilStable;
49
53
  }
@@ -2,7 +2,7 @@ import { makeAutoObservable } from 'mobx';
2
2
  import { SnapshotsStore, } from '@postgres.ai/shared/stores/Snapshots';
3
3
  import { getTextFromUnknownApiError } from '@postgres.ai/shared/utils/api';
4
4
  import { checkIsCloneStable } from '@postgres.ai/shared/utils/clone';
5
- const UNSTABLE_CLONE_UPDATE_TIMEOUT = 1000;
5
+ import { ClonePoller } from '@postgres.ai/shared/utils/clonePoller';
6
6
  export class MainStore {
7
7
  constructor(api) {
8
8
  this.instance = null;
@@ -11,6 +11,7 @@ export class MainStore {
11
11
  this.getSnapshotsError = null;
12
12
  this.clone = null;
13
13
  this.cloneError = null;
14
+ this.poller = new ClonePoller();
14
15
  this.load = async (instanceId) => {
15
16
  var _a, _b;
16
17
  const [instance, isLoadedSnapshots] = await Promise.all([
@@ -26,6 +27,7 @@ export class MainStore {
26
27
  this.createClone = async (data) => {
27
28
  if (!this.instance)
28
29
  return false;
30
+ this.poller.start();
29
31
  const { response, error } = await this.api.createClone({
30
32
  ...data,
31
33
  instanceId: this.instance.id,
@@ -61,13 +63,16 @@ export class MainStore {
61
63
  this.getSnapshotsError = await error.json().then((err) => err);
62
64
  return response;
63
65
  };
66
+ this.stopPolling = () => this.poller.stop();
64
67
  this.updateCloneUntilStable = async (args) => {
65
- window.clearTimeout(this.cloneUpdateTimeout);
68
+ if (this.poller.isStopped)
69
+ return;
70
+ this.poller.cancel();
66
71
  const { response, error } = await this.api.getClone(args);
67
72
  if (response) {
68
73
  this.clone = response;
69
74
  if (!this.isCloneStable)
70
- this.cloneUpdateTimeout = window.setTimeout(() => this.updateCloneUntilStable(args), UNSTABLE_CLONE_UPDATE_TIMEOUT);
75
+ this.poller.scheduleNext(() => this.updateCloneUntilStable(args));
71
76
  }
72
77
  if (error)
73
78
  this.cloneError = await error.json().then((err) => (err === null || err === void 0 ? void 0 : err.message) || err);
@@ -0,0 +1,11 @@
1
+ /// <reference types="react" />
2
+ import { PhysicalEnv } from '../../useForm';
3
+ declare type Props = {
4
+ envs: PhysicalEnv[];
5
+ onChange: (envs: PhysicalEnv[]) => void;
6
+ suggestions?: string[];
7
+ disabled?: boolean;
8
+ keyErrors?: (string | undefined)[];
9
+ };
10
+ export declare const EnvsEditor: ({ envs, onChange, suggestions, disabled, keyErrors, }: Props) => JSX.Element;
11
+ export {};
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, IconButton, TextField, Typography } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ // EnvsEditor renders a rows-of-key/value editor with add/remove and a
5
+ // click-to-add suggestion list. Engine consumes envs as a free-form map
6
+ // (physical.go:76, CopyOptions.Envs map[string]string); the UI is a thin
7
+ // surface over that map.
8
+ export const EnvsEditor = ({ envs, onChange, suggestions = [], disabled, keyErrors = [], }) => {
9
+ const updateRow = (i, patch) => {
10
+ const next = envs.slice();
11
+ next[i] = { ...next[i], ...patch };
12
+ onChange(next);
13
+ };
14
+ const removeRow = (i) => {
15
+ const next = envs.slice();
16
+ next.splice(i, 1);
17
+ onChange(next);
18
+ };
19
+ const addRow = (key = '') => {
20
+ onChange([...envs, { key, value: '' }]);
21
+ };
22
+ const usedKeys = new Set(envs.map((e) => e.key));
23
+ return (_jsxs(Box, { mt: 1, "data-testid": "envs-editor", children: [_jsx(Typography, { variant: "subtitle2", children: "Environment variables" }), envs.length === 0 ? (_jsx(Box, { mt: 1, mb: 1, children: _jsx(Typography, { variant: "caption", color: "textSecondary", children: "No environment variables set. Use suggestions below or click \"Add\"." }) })) : (envs.map((env, i) => (_jsxs(Box, { display: "flex", alignItems: "center", mt: 1, "data-testid": `envs-row-${i}`, children: [_jsx(TextField, { size: "small", label: "Name", value: env.key, disabled: disabled, error: Boolean(keyErrors[i]), helperText: keyErrors[i], onChange: (e) => updateRow(i, { key: e.target.value }), inputProps: { 'data-testid': `envs-key-${i}` } }), _jsx(Box, { mx: 1, children: _jsx(TextField, { size: "small", label: "Value", value: env.value, disabled: disabled, onChange: (e) => updateRow(i, { value: e.target.value }), inputProps: { 'data-testid': `envs-value-${i}` } }) }), _jsx(IconButton, { size: "small", "aria-label": "remove env", disabled: disabled, onClick: () => removeRow(i), "data-testid": `envs-remove-${i}`, children: "\u00D7" })] }, i)))), _jsx(Box, { mt: 1, children: _jsx(Button, { size: "small", variant: "outlined", disabled: disabled, onClick: () => addRow(), "data-testid": "envs-add", children: "+ Add" }) }), suggestions.length > 0 && (_jsxs(Box, { mt: 1, children: [_jsx(Typography, { variant: "caption", color: "textSecondary", children: "Suggestions:" }), _jsx(Box, { mt: 0.5, children: suggestions.map((s) => (_jsx(Button, { size: "small", variant: "text", disabled: disabled || usedKeys.has(s), onClick: () => addRow(s), "data-testid": `envs-suggest-${s}`, children: s }, s))) })] }))] }));
24
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { FormValues } from '../../useForm';
3
+ declare type Props = {
4
+ values: FormValues;
5
+ onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void;
6
+ disabled?: boolean;
7
+ envsKeyErrors?: (string | undefined)[];
8
+ };
9
+ export declare const PgBackRest: ({ values, onChange, disabled, envsKeyErrors, }: Props) => JSX.Element;
10
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox, FormControlLabel, TextField, Typography, } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ import { EnvsEditor } from '../EnvsEditor';
5
+ // pgBackRest exposes two structured fields (stanza, delta); repo paths, S3
6
+ // keys, archive options all live in the envs map. See
7
+ // engine/internal/retrieval/engine/postgres/physical/pgbackrest.go:23-26 and
8
+ // the example envs in config.example.physical_pgbackrest.yml:84-99.
9
+ const PGBACKREST_ENV_SUGGESTIONS = [
10
+ 'PGBACKREST_REPO',
11
+ 'PGBACKREST_REPO1_TYPE',
12
+ 'PGBACKREST_REPO1_PATH',
13
+ 'PGBACKREST_REPO1_HOST',
14
+ 'PGBACKREST_REPO1_HOST_USER',
15
+ 'PGBACKREST_REPO1_S3_BUCKET',
16
+ 'PGBACKREST_REPO1_S3_ENDPOINT',
17
+ 'PGBACKREST_REPO1_S3_KEY',
18
+ 'PGBACKREST_REPO1_S3_KEY_SECRET',
19
+ 'PGBACKREST_REPO1_S3_REGION',
20
+ 'PGBACKREST_LOG_LEVEL_CONSOLE',
21
+ 'PGBACKREST_PROCESS_MAX',
22
+ ];
23
+ export const PgBackRest = ({ values, onChange, disabled, envsKeyErrors, }) => {
24
+ const onEnvsChange = (envs) => onChange('physicalEnvs', envs);
25
+ return (_jsxs(Box, { mt: 2, "data-testid": "pgbackrest-form", children: [_jsx(Typography, { variant: "subtitle1", children: "pgBackRest" }), _jsx(Box, { mt: 1, children: _jsx(TextField, { fullWidth: true, label: "Stanza", placeholder: "my-stanza", value: values.physicalPgbackrestStanza, disabled: disabled, onChange: (e) => onChange('physicalPgbackrestStanza', e.target.value), helperText: "Stanza name (must match the stanza configured in your pgBackRest setup).", inputProps: { 'data-testid': 'pgbackrest-stanza' } }) }), _jsx(Box, { mt: 1, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: values.physicalPgbackrestDelta, disabled: disabled, onChange: (e) => onChange('physicalPgbackrestDelta', e.target.checked), inputProps: {
26
+ 'aria-label': 'delta',
27
+ 'data-testid': 'pgbackrest-delta',
28
+ } }), label: "Delta restore" }) }), _jsx(EnvsEditor, { envs: values.physicalEnvs, onChange: onEnvsChange, suggestions: PGBACKREST_ENV_SUGGESTIONS, disabled: disabled, keyErrors: envsKeyErrors })] }));
29
+ };
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ import { FormValues } from '../../useForm';
3
+ declare type Props = {
4
+ values: FormValues;
5
+ onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void;
6
+ disabled?: boolean;
7
+ };
8
+ export declare const Sync: ({ values, onChange, disabled }: Props) => JSX.Element;
9
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox, FormControlLabel, TextField, Typography, } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ // Shared section rendered below WAL-G / pgBackRest with the structured fields
5
+ // that apply across all physical sub-tools (sync.enabled and dockerImage). The
6
+ // engine surfaces more knobs (sync.healthCheck, sync.configs, recovery target,
7
+ // custom restore command), but they remain YAML-only for 4.2 to keep the
8
+ // projection footprint flat.
9
+ export const Sync = ({ values, onChange, disabled }) => {
10
+ return (_jsxs(Box, { mt: 2, "data-testid": "physical-sync", children: [_jsx(Typography, { variant: "subtitle1", children: "Sync container & image" }), _jsx(Box, { mt: 1, children: _jsx(TextField, { fullWidth: true, label: "Docker image", placeholder: "postgresai/extended-postgres:18-0.6.2", value: values.physicalDockerImage, disabled: disabled, onChange: (e) => onChange('physicalDockerImage', e.target.value), helperText: "Postgres image for restore/sync containers. Major version must match the source.", inputProps: { 'data-testid': 'physical-docker-image' } }) }), _jsx(Box, { mt: 1, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: values.physicalSyncEnabled, disabled: disabled, onChange: (e) => onChange('physicalSyncEnabled', e.target.checked), inputProps: {
11
+ 'aria-label': 'sync enabled',
12
+ 'data-testid': 'physical-sync-enabled',
13
+ } }), label: "Run sync container" }) }), _jsx(Box, { mt: 1, children: _jsx(Typography, { variant: "caption", color: "textSecondary", children: "For advanced sync settings (health check, sync postgres configs, recovery target), edit the YAML config directly." }) })] }));
14
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { FormValues } from '../../useForm';
3
+ declare type Props = {
4
+ values: FormValues;
5
+ onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void;
6
+ disabled?: boolean;
7
+ envsKeyErrors?: (string | undefined)[];
8
+ };
9
+ export declare const Walg: ({ values, onChange, disabled, envsKeyErrors }: Props) => JSX.Element;
10
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextField, Typography } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ import { EnvsEditor } from '../EnvsEditor';
5
+ // WAL-G has one structured field (BackupName, defaulting to "LATEST"); storage
6
+ // backend, bucket, prefix, and credentials all live in the envs map. See
7
+ // engine/internal/retrieval/engine/postgres/physical/wal_g.go:36-38 and the
8
+ // example envs in config.example.physical_walg.yml:84-86.
9
+ const WALG_ENV_SUGGESTIONS = [
10
+ 'WALG_GS_PREFIX',
11
+ 'WALG_S3_PREFIX',
12
+ 'WALG_FILE_PREFIX',
13
+ 'GOOGLE_APPLICATION_CREDENTIALS',
14
+ 'AWS_ACCESS_KEY_ID',
15
+ 'AWS_SECRET_ACCESS_KEY',
16
+ 'AWS_REGION',
17
+ ];
18
+ export const Walg = ({ values, onChange, disabled, envsKeyErrors }) => {
19
+ const onEnvsChange = (envs) => onChange('physicalEnvs', envs);
20
+ return (_jsxs(Box, { mt: 2, "data-testid": "walg-form", children: [_jsx(Typography, { variant: "subtitle1", children: "WAL-G" }), _jsx(Box, { mt: 1, children: _jsx(TextField, { fullWidth: true, label: "Backup name", placeholder: "LATEST", value: values.physicalWalgBackupName, disabled: disabled, onChange: (e) => onChange('physicalWalgBackupName', e.target.value), helperText: 'Which backup to restore. "LATEST" picks the most recent.', inputProps: { 'data-testid': 'walg-backup-name' } }) }), _jsx(Box, { mt: 2, children: _jsx(Typography, { variant: "caption", color: "textSecondary", children: "Storage backend, bucket, prefix, and credentials all live in the envs map. Do not paste credentials into the backup name field or any URL." }) }), _jsx(EnvsEditor, { envs: values.physicalEnvs, onChange: onEnvsChange, suggestions: WALG_ENV_SUGGESTIONS, disabled: disabled, keyErrors: envsKeyErrors })] }));
21
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { FormValues } from '../useForm';
3
+ declare type Props = {
4
+ values: FormValues;
5
+ onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void;
6
+ disabled?: boolean;
7
+ envsKeyErrors?: (string | undefined)[];
8
+ };
9
+ export declare const PhysicalMode: ({ values, onChange, disabled, envsKeyErrors, }: Props) => JSX.Element;
10
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FormControl, FormControlLabel, Radio, RadioGroup, Typography, } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ import { PgBackRest } from './PgBackRest';
5
+ import { Sync } from './Sync';
6
+ import { Walg } from './Walg';
7
+ // Physical-mode restore tool selector + sub-form. Two values surface in the
8
+ // UI: WAL-G and pgBackRest, the two tool values the engine accepts outside
9
+ // customTool. pg_basebackup is invoked via the customTool path and remains
10
+ // YAML-only; when the loaded projection has `tool: customTool` we render a
11
+ // banner instead of the sub-form, keeping the user from accidentally wiping a
12
+ // hand-edited customTool config.
13
+ export const PhysicalMode = ({ values, onChange, disabled, envsKeyErrors, }) => {
14
+ const tool = values.physicalTool;
15
+ const isCustomTool = tool === 'customTool';
16
+ return (_jsxs(Box, { mt: 1, "data-testid": "physical-mode", children: [_jsx(Typography, { variant: "subtitle2", children: "Restore tool" }), isCustomTool ? (_jsx(Box, { mt: 1, "data-testid": "physical-custom-tool-banner", children: _jsx(Typography, { variant: "body2", color: "textSecondary", children: "This config uses a custom restore tool (e.g. pg_basebackup). The UI cannot edit customTool configurations \u2014 edit the YAML directly." }) })) : (_jsx(FormControl, { children: _jsxs(RadioGroup, { row: true, value: tool, onChange: (_, value) => onChange('physicalTool', value), "aria-label": "physical restore tool", children: [_jsx(FormControlLabel, { value: "walg", control: _jsx(Radio, { disabled: disabled }), label: "WAL-G" }), _jsx(FormControlLabel, { value: "pgbackrest", control: _jsx(Radio, { disabled: disabled }), label: "pgBackRest" })] }) })), !isCustomTool && tool === 'walg' && (_jsx(Walg, { values: values, onChange: onChange, disabled: disabled, envsKeyErrors: envsKeyErrors })), !isCustomTool && tool === 'pgbackrest' && (_jsx(PgBackRest, { values: values, onChange: onChange, disabled: disabled, envsKeyErrors: envsKeyErrors })), !isCustomTool && tool && (_jsx(Sync, { values: values, onChange: onChange, disabled: disabled }))] }));
17
+ };
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ import { ProposedConfig } from '@postgres.ai/shared/types/api/endpoints/probeSource';
3
+ import { ResolvedImage } from '../configOptions';
4
+ declare type Props = {
5
+ proposed: ProposedConfig;
6
+ resolved: ResolvedImage;
7
+ applying: boolean;
8
+ applyError: string | null;
9
+ onApply: () => void;
10
+ onEdit: () => void;
11
+ };
12
+ export declare const PreviewCard: ({ proposed, resolved, applying, applyError, onApply, onEdit, }: Props) => JSX.Element;
13
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Link, Typography } from '@material-ui/core';
3
+ import Box from '@mui/material/Box';
4
+ import { Spinner } from '@postgres.ai/shared/components/Spinner';
5
+ import { providerKeyToImage } from '../configOptions';
6
+ const Field = ({ label, value }) => (_jsxs(Box, { display: "flex", mb: 0.5, children: [_jsx(Box, { minWidth: 220, fontWeight: 600, children: label }), _jsx(Box, { minWidth: 0, style: { overflowWrap: 'anywhere' }, children: value })] }));
7
+ const Callout = ({ children }) => (_jsx(Box, { mt: 1, p: 1, bgcolor: "#fff8e1", borderLeft: "4px solid #f5a623", fontSize: 13, style: { overflowWrap: 'anywhere' }, children: children }));
8
+ export const PreviewCard = ({ proposed, resolved, applying, applyError, onApply, onEdit, }) => {
9
+ var _a, _b, _c;
10
+ const mapping = providerKeyToImage(proposed.dockerImage, proposed.pgMajorVersion);
11
+ const preloadLibraries = (_a = resolved.sharedPreloadLibraries) !== null && _a !== void 0 ? _a : proposed.sharedPreloadLibraries;
12
+ const tuningEntries = Object.entries((_b = proposed.queryTuning) !== null && _b !== void 0 ? _b : {});
13
+ return (_jsxs(Box, { mt: 2, p: 2, border: "1px solid #e0e0e0", borderRadius: 4, "data-testid": "preview-card", children: [_jsx(Typography, { variant: "h6", children: "Proposed configuration" }), _jsxs(Box, { mt: 2, children: [_jsx(Field, { label: "Detected provider", value: proposed.detectedProvider || 'unknown' }), _jsx(Field, { label: "Docker image", value: resolved.dockerPath || mapping.imageType }), _jsx(Field, { label: "Postgres major version", value: String(proposed.pgMajorVersion || 'unknown') }), _jsx(Field, { label: "Databases", value: ((_c = proposed.databases) === null || _c === void 0 ? void 0 : _c.join(', ')) || '(none)' }), _jsx(Field, { label: "shared_buffers", value: proposed.sharedBuffers || '' }), _jsx(Field, { label: "shared_preload_libraries", value: preloadLibraries || '' })] }), tuningEntries.length > 0 && (_jsxs(Box, { mt: 2, children: [_jsx(Typography, { variant: "subtitle2", children: "Query tuning" }), _jsx(Box, { component: "table", mt: 1, style: { borderCollapse: 'collapse' }, children: _jsx("tbody", { children: tuningEntries.map(([k, v]) => (_jsxs("tr", { children: [_jsx("td", { style: { padding: '2px 16px 2px 0', fontWeight: 600 }, children: k }), _jsx("td", { style: { padding: '2px 0' }, children: v })] }, k))) }) })] })), _jsxs(Box, { mt: 2, children: [(mapping.fallback || proposed.detectedProvider === 'generic') && (_jsx(Callout, { children: "Could not detect a managed cloud provider; using the generic Postgres image. Switch to Expert mode if your source runs on a managed service and we missed it." })), !proposed.memoryProbed && (_jsxs(Callout, { children: ["Could not detect host memory; ", _jsx("code", { children: "shared_buffers" }), " is set to a 1\u00A0GB safe default. Adjust in Expert mode if your host has more RAM."] })), _jsx(Callout, { children: "Query tuning is copied from your source. If you use the RDS refresh tool, these values may not match production \u2014 review in Expert mode after the first retrieval run." }), resolved.isSe ? (_jsxs(Callout, { children: ["Shipping the ", _jsx("code", { children: resolved.dockerPath }), " image with its curated ", _jsx("code", { children: "shared_preload_libraries" }), " preset, so the detected extensions are bundled."] })) : (_jsxs(Callout, { children: ["We'll ship ", _jsx("code", { children: preloadLibraries }), ". If the chosen image does not bundle one of these libraries, the clone container will fail to start with a \"could not load library\" error \u2014 check", ' ', _jsx("code", { children: "docker logs dblab_server" }), " after Apply."] }))] }), _jsxs(Box, { mt: 2, display: "flex", alignItems: "center", children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: onApply, disabled: applying, "data-testid": "preview-apply", children: ["Apply & start retrieval", applying && _jsx(Spinner, { size: "sm" })] }), _jsx(Box, { ml: 2, children: _jsx(Link, { component: "button", type: "button", onClick: onEdit, "data-testid": "preview-edit", children: "Edit before applying" }) })] }), applyError && (_jsx(Box, { mt: 1, color: "#d32f2f", fontSize: 13, "data-testid": "apply-error", children: applyError }))] }));
14
+ };
@@ -0,0 +1,19 @@
1
+ /// <reference types="react" />
2
+ import { SeImages } from '@postgres.ai/shared/types/api/endpoints/getSeImages';
3
+ import { ProposedConfig } from '@postgres.ai/shared/types/api/endpoints/probeSource';
4
+ import { ResolvedImage } from '../configOptions';
5
+ import { FormValues } from '../useForm';
6
+ declare type Props = {
7
+ instanceId: string;
8
+ disabled?: boolean;
9
+ onApplied?: () => void;
10
+ onEdit?: (proposed: ProposedConfig, password: string) => void;
11
+ };
12
+ export declare const buildProjectionFromProposed: (proposed: ProposedConfig, password: string, resolved?: ResolvedImage) => FormValues;
13
+ export declare const resolveProbeImage: (proposed: ProposedConfig, getSeImages: (args: {
14
+ packageGroup: string;
15
+ }) => Promise<SeImages[] | null | undefined>) => Promise<ResolvedImage>;
16
+ export declare const SimpleMode: (({ instanceId, disabled, onApplied, onEdit }: Props) => JSX.Element) & {
17
+ displayName: string;
18
+ };
19
+ export {};
@@ -0,0 +1,166 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { observer } from 'mobx-react-lite';
4
+ import { Button, TextField, Typography } from '@material-ui/core';
5
+ import Box from '@mui/material/Box';
6
+ import { Spinner } from '@postgres.ai/shared/components/Spinner';
7
+ import { useStores } from '@postgres.ai/shared/pages/Instance/context';
8
+ import { providerKeyToImage, resolveDockerImagePath, selectSeImage, } from '../configOptions';
9
+ import { PreviewCard } from './PreviewCard';
10
+ // genericFallbackImage resolves the generic CE image for a probed source. Used
11
+ // when no SE image is available (CE, or a managed provider with no SE image for
12
+ // the detected major version).
13
+ const genericFallbackImage = (proposed) => {
14
+ const mapping = providerKeyToImage(proposed.dockerImage, proposed.pgMajorVersion);
15
+ const isGeneric = mapping.imageType === 'Generic Postgres';
16
+ return {
17
+ dockerPath: resolveDockerImagePath(proposed.dockerImage, proposed.pgMajorVersion, proposed.dockerTag),
18
+ dockerTag: isGeneric ? proposed.dockerTag || mapping.defaultTag || '' : '',
19
+ sharedPreloadLibraries: proposed.sharedPreloadLibraries,
20
+ isSe: false,
21
+ };
22
+ };
23
+ // Translates a ProposedConfig from POST /admin/probe-source into the
24
+ // FormValues shape the Expert form uses. Both Apply (→ updateConfig) and
25
+ // Edit (→ formik.setValues) consume it, so the engine receives the same
26
+ // projection regardless of which flow the user picks. The resolved image (SE
27
+ // or generic) is passed in so the preview and the applied config never diverge.
28
+ export const buildProjectionFromProposed = (proposed, password, resolved) => {
29
+ var _a;
30
+ const mapping = providerKeyToImage(proposed.dockerImage, proposed.pgMajorVersion);
31
+ const isGeneric = mapping.imageType === 'Generic Postgres';
32
+ const image = resolved !== null && resolved !== void 0 ? resolved : genericFallbackImage(proposed);
33
+ return {
34
+ debug: false,
35
+ dockerImage: isGeneric
36
+ ? String(proposed.pgMajorVersion)
37
+ : mapping.imageType,
38
+ dockerImageType: mapping.imageType,
39
+ dockerPath: image.dockerPath,
40
+ dockerTag: image.dockerTag,
41
+ sharedBuffers: proposed.sharedBuffers,
42
+ sharedPreloadLibraries: (_a = image.sharedPreloadLibraries) !== null && _a !== void 0 ? _a : proposed.sharedPreloadLibraries,
43
+ // tuningParams is typed as string on FormValues but updateConfig.ts
44
+ // spreads it as a key-value object; cast matches the Expert form's
45
+ // formatTuningParamsToObj(...) as unknown as string pattern.
46
+ tuningParams: { ...proposed.queryTuning },
47
+ timetable: '0 0 * * *',
48
+ dbname: proposed.source.dbname,
49
+ host: proposed.source.host,
50
+ port: String(proposed.source.port),
51
+ username: proposed.source.username,
52
+ password,
53
+ databases: proposed.databases.join(' '),
54
+ dumpParallelJobs: '',
55
+ dumpIgnoreErrors: false,
56
+ restoreParallelJobs: '',
57
+ // managed sources reference cloud-only extensions/objects (e.g. supabase_vault)
58
+ // that are absent from the clone image; best-effort restore skips them.
59
+ restoreIgnoreErrors: true,
60
+ restoreConfigs: '',
61
+ pgDumpCustomOptions: '',
62
+ // skip ownership and privileges on restore: managed sources (Supabase, RDS)
63
+ // reference roles that do not exist in the clone, which would abort pg_restore.
64
+ pgRestoreCustomOptions: '--no-owner --no-privileges --no-tablespaces',
65
+ retrievalMode: 'logical',
66
+ physicalTool: '',
67
+ physicalDockerImage: '',
68
+ physicalSyncEnabled: false,
69
+ physicalWalgBackupName: '',
70
+ physicalPgbackrestStanza: '',
71
+ physicalPgbackrestDelta: false,
72
+ physicalEnvs: [],
73
+ };
74
+ };
75
+ // resolveProbeImage selects the platform SE image for a managed provider when
76
+ // the SE catalog is reachable — SE/Enterprise instances expose it (platformUrl
77
+ // set), CE returns undefined. Falls back to the generic image for CE and for
78
+ // managed providers with no SE image at the detected major version.
79
+ export const resolveProbeImage = async (proposed, getSeImages) => {
80
+ var _a;
81
+ const mapping = providerKeyToImage(proposed.dockerImage, proposed.pgMajorVersion);
82
+ if (mapping.imageType !== 'Generic Postgres') {
83
+ const seImages = await getSeImages({ packageGroup: mapping.imageType });
84
+ const se = selectSeImage(seImages, proposed.pgMajorVersion);
85
+ if (se) {
86
+ return {
87
+ dockerPath: se.location,
88
+ dockerTag: se.tag,
89
+ sharedPreloadLibraries: (_a = se.pg_config_presets) === null || _a === void 0 ? void 0 : _a.shared_preload_libraries,
90
+ isSe: true,
91
+ };
92
+ }
93
+ }
94
+ return genericFallbackImage(proposed);
95
+ };
96
+ export const SimpleMode = observer(({ instanceId, disabled, onApplied, onEdit }) => {
97
+ const stores = useStores();
98
+ const main = stores.main;
99
+ const [url, setUrl] = useState('');
100
+ const [password, setPassword] = useState('');
101
+ const [probing, setProbing] = useState(false);
102
+ const [probeError, setProbeError] = useState(null);
103
+ const [proposed, setProposed] = useState(null);
104
+ const [resolved, setResolved] = useState(null);
105
+ const [applying, setApplying] = useState(false);
106
+ const [applyError, setApplyError] = useState(null);
107
+ const onDetect = async () => {
108
+ setProbing(true);
109
+ setProbeError(null);
110
+ setProposed(null);
111
+ setResolved(null);
112
+ setApplyError(null);
113
+ const result = await main.probeSource({ url, password });
114
+ if (!result) {
115
+ setProbing(false);
116
+ setProbeError('Probe is not available on this instance.');
117
+ return;
118
+ }
119
+ if (result.error) {
120
+ setProbing(false);
121
+ setProbeError(result.error.message);
122
+ return;
123
+ }
124
+ if (result.response) {
125
+ const image = await resolveProbeImage(result.response, main.getSeImages);
126
+ setResolved(image);
127
+ setProposed(result.response);
128
+ }
129
+ setProbing(false);
130
+ };
131
+ const onApply = async () => {
132
+ var _a;
133
+ if (!proposed || !resolved)
134
+ return;
135
+ setApplying(true);
136
+ setApplyError(null);
137
+ try {
138
+ const projection = buildProjectionFromProposed(proposed, password, resolved);
139
+ const response = await main.updateConfig(projection, instanceId);
140
+ if (!response) {
141
+ setApplyError((_a = main.configError) !== null && _a !== void 0 ? _a : 'Could not apply the proposed configuration.');
142
+ return;
143
+ }
144
+ const refresh = await main.fullRefresh(instanceId);
145
+ if (refresh === null || refresh === void 0 ? void 0 : refresh.error) {
146
+ setApplyError(`Configuration applied, but starting data retrieval failed: ${refresh.error.message}`);
147
+ return;
148
+ }
149
+ onApplied === null || onApplied === void 0 ? void 0 : onApplied();
150
+ }
151
+ catch (err) {
152
+ setApplyError(err instanceof Error
153
+ ? err.message
154
+ : 'Could not apply the proposed configuration.');
155
+ }
156
+ finally {
157
+ setApplying(false);
158
+ }
159
+ };
160
+ const handleEdit = () => {
161
+ if (proposed)
162
+ onEdit === null || onEdit === void 0 ? void 0 : onEdit(proposed, password);
163
+ };
164
+ const canDetect = !probing && url.trim().length > 0 && password.length > 0 && !disabled;
165
+ return (_jsxs(Box, { mt: 2, mb: 2, "data-testid": "simple-mode", children: [!proposed && (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", children: "Simple configuration" }), _jsx(Typography, { variant: "body2", children: "Paste your source connection string and password. We'll probe the source, propose a configuration, and let you review before starting retrieval." }), _jsx(Box, { mt: 2, children: _jsx(TextField, { label: "Connection string", placeholder: "postgres://user@host:5432/dbname", value: url, onChange: (e) => setUrl(e.target.value), fullWidth: true, disabled: probing || disabled, inputProps: { 'data-testid': 'simple-url' } }) }), _jsx(Box, { mt: 2, children: _jsx(TextField, { label: "Password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), fullWidth: true, disabled: probing || disabled, inputProps: { 'data-testid': 'simple-password' } }) }), _jsx(Box, { mt: 2, children: _jsxs(Button, { variant: "contained", color: "secondary", onClick: onDetect, disabled: !canDetect, "data-testid": "simple-detect", children: ["Detect & preview", probing && _jsx(Spinner, { size: "sm" })] }) }), probeError && (_jsx(Box, { mt: 1, color: "#d32f2f", fontSize: 13, "data-testid": "probe-error", children: probeError }))] })), proposed && resolved && (_jsx(PreviewCard, { proposed: proposed, resolved: resolved, applying: applying, applyError: applyError, onApply: onApply, onEdit: handleEdit }))] }));
166
+ });
@@ -0,0 +1,2 @@
1
+ export declare type ConfigMode = 'simple' | 'expert';
2
+ export declare const getInitialConfigMode: (host: string | undefined, retrievalMode?: string) => ConfigMode;
@@ -0,0 +1,7 @@
1
+ // Picks the default Configuration tab when the page first finishes loading
2
+ // the server config. An unconfigured instance lands on Simple; an instance
3
+ // that already has a source host filled in OR a non-logical retrieval mode
4
+ // configured lands on Expert so the user sees the form they previously
5
+ // interacted with. Physical-mode users do not have a host field but still
6
+ // need to land on Expert because Simple-mode targets logical retrieval.
7
+ export const getInitialConfigMode = (host, retrievalMode) => (host || retrievalMode === 'physical' ? 'expert' : 'simple');
@@ -1,3 +1,19 @@
1
+ import { SeImages } from '@postgres.ai/shared/types/api/endpoints/getSeImages';
2
+ export declare type ProviderImageMapping = {
3
+ imageType: string;
4
+ defaultTag?: string;
5
+ fallback: boolean;
6
+ };
7
+ export declare const genericImagePathForVersion: (pgMajorVersion: number) => string;
8
+ export declare type ResolvedImage = {
9
+ dockerPath: string;
10
+ dockerTag: string;
11
+ sharedPreloadLibraries?: string;
12
+ isSe: boolean;
13
+ };
14
+ export declare const selectSeImage: (seImages: SeImages[] | null | undefined, pgMajorVersion: number) => SeImages | undefined;
15
+ export declare const resolveDockerImagePath: (providerKey: string, pgMajorVersion: number, dockerTag?: string) => string;
16
+ export declare const providerKeyToImage: (providerKey: string, pgMajorVersion: number) => ProviderImageMapping;
1
17
  export declare const dockerImageOptions: {
2
18
  name: string;
3
19
  type: string;