@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.
- package/.gitlab-ci.yml +7 -3
- package/components/UpgradeCloneModal/index.tsx +145 -0
- package/dist/components/UpgradeCloneModal/index.d.ts +11 -0
- package/dist/components/UpgradeCloneModal/index.js +75 -0
- package/dist/package.json +2 -2
- package/dist/pages/Branches/Branch/index.js +27 -2
- package/dist/pages/Branches/Branch/stores/Main.d.ts +5 -0
- package/dist/pages/Branches/Branch/stores/Main.js +29 -0
- package/dist/pages/Branches/components/BranchesTable/index.js +2 -2
- package/dist/pages/Branches/index.js +10 -1
- package/dist/pages/Clone/Status/index.js +3 -0
- package/dist/pages/Clone/index.js +12 -4
- package/dist/pages/Clone/stores/Main.d.ts +9 -1
- package/dist/pages/Clone/stores/Main.js +40 -3
- package/dist/pages/CreateBranch/stores/Main.d.ts +3 -0
- package/dist/pages/CreateClone/index.js +2 -0
- package/dist/pages/CreateClone/stores/Main.d.ts +5 -1
- package/dist/pages/CreateClone/stores/Main.js +8 -3
- package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
- package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +13 -0
- package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
- package/dist/pages/Instance/Configuration/SimpleMode/index.d.ts +19 -0
- package/dist/pages/Instance/Configuration/SimpleMode/index.js +166 -0
- package/dist/pages/Instance/Configuration/configMode.d.ts +2 -0
- package/dist/pages/Instance/Configuration/configMode.js +7 -0
- package/dist/pages/Instance/Configuration/configOptions.d.ts +16 -0
- package/dist/pages/Instance/Configuration/configOptions.js +71 -0
- package/dist/pages/Instance/Configuration/connectionString.d.ts +20 -0
- package/dist/pages/Instance/Configuration/connectionString.js +129 -0
- package/dist/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
- package/dist/pages/Instance/Configuration/dockerCatalog.js +19 -0
- package/dist/pages/Instance/Configuration/index.d.ts +1 -2
- package/dist/pages/Instance/Configuration/index.js +115 -86
- package/dist/pages/Instance/Configuration/useForm.d.ts +20 -0
- package/dist/pages/Instance/Configuration/useForm.js +126 -5
- package/dist/pages/Instance/Configuration/utils/index.js +1 -17
- package/dist/pages/Instance/Info/Snapshots/Calendar/Day/index.js +3 -3
- package/dist/pages/Instance/Info/Snapshots/Calendar/utils.d.ts +3 -0
- package/dist/pages/Instance/Info/Snapshots/utils.d.ts +6 -0
- package/dist/pages/Instance/Snapshots/components/SnapshotsList/index.js +11 -1
- package/dist/pages/Instance/Snapshots/index.js +11 -3
- package/dist/pages/Instance/Snapshots/utils/index.d.ts +3 -0
- package/dist/pages/Instance/Tabs/styles.js +6 -6
- package/dist/pages/Instance/index.js +1 -3
- package/dist/pages/Instance/stores/Main.d.ts +22 -2
- package/dist/pages/Instance/stores/Main.js +26 -9
- package/dist/pages/Snapshots/Snapshot/index.js +28 -2
- package/dist/pages/Snapshots/Snapshot/stores/Main.d.ts +5 -0
- package/dist/pages/Snapshots/Snapshot/stores/Main.js +29 -0
- package/dist/stores/Snapshots.d.ts +1 -1
- package/dist/stores/Snapshots.js +12 -6
- package/dist/types/api/endpoints/getBranches.d.ts +6 -0
- package/dist/types/api/endpoints/probeSource.d.ts +32 -0
- package/dist/types/api/endpoints/probeSource.js +1 -0
- package/dist/types/api/endpoints/testDbSource.d.ts +3 -1
- package/dist/types/api/endpoints/testDbSource.js +6 -0
- package/dist/types/api/endpoints/updateBranch.d.ts +11 -0
- package/dist/types/api/endpoints/updateBranch.js +1 -0
- package/dist/types/api/endpoints/updateSnapshot.d.ts +11 -0
- package/dist/types/api/endpoints/updateSnapshot.js +1 -0
- package/dist/types/api/endpoints/upgradeClone.d.ts +8 -0
- package/dist/types/api/endpoints/upgradeClone.js +1 -0
- package/dist/types/api/entities/branchSnapshots.d.ts +3 -0
- package/dist/types/api/entities/clone.d.ts +9 -2
- package/dist/types/api/entities/config.d.ts +32 -0
- package/dist/types/api/entities/config.js +45 -18
- package/dist/types/api/entities/instance.d.ts +11 -1
- package/dist/types/api/entities/instanceState.d.ts +16 -1
- package/dist/types/api/entities/snapshot.d.ts +6 -0
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.js +6 -0
- package/dist/utils/clone.d.ts +3 -1
- package/dist/utils/clone.js +7 -1
- package/dist/utils/clonePoller.d.ts +12 -0
- package/dist/utils/clonePoller.js +41 -0
- package/dist/utils/date.js +12 -12
- package/dist/utils/protection.d.ts +5 -0
- package/dist/utils/protection.js +23 -0
- package/package.json +2 -2
- package/pages/Branches/Branch/index.tsx +70 -0
- package/pages/Branches/Branch/stores/Main.ts +40 -0
- package/pages/Branches/components/BranchesTable/index.tsx +2 -0
- package/pages/Branches/index.tsx +13 -1
- package/pages/Clone/Status/index.tsx +3 -0
- package/pages/Clone/index.tsx +58 -0
- package/pages/Clone/stores/Main.ts +51 -8
- package/pages/CreateClone/index.tsx +3 -0
- package/pages/CreateClone/stores/Main.ts +10 -8
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.tsx +129 -0
- package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.tsx +89 -0
- package/pages/Instance/Configuration/PhysicalMode/Sync/index.tsx +64 -0
- package/pages/Instance/Configuration/PhysicalMode/Walg/index.tsx +63 -0
- package/pages/Instance/Configuration/PhysicalMode/index.tsx +93 -0
- package/pages/Instance/Configuration/SimpleMode/PreviewCard.tsx +176 -0
- package/pages/Instance/Configuration/SimpleMode/index.tsx +303 -0
- package/pages/Instance/Configuration/configMode.ts +12 -0
- package/pages/Instance/Configuration/configOptions.ts +111 -0
- package/pages/Instance/Configuration/connectionString.ts +157 -0
- package/pages/Instance/Configuration/dockerCatalog.ts +21 -0
- package/pages/Instance/Configuration/index.tsx +151 -49
- package/pages/Instance/Configuration/useForm.ts +155 -5
- package/pages/Instance/Configuration/utils/index.ts +1 -17
- package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +3 -3
- package/pages/Instance/Snapshots/components/SnapshotsList/index.tsx +21 -1
- package/pages/Instance/Snapshots/index.tsx +15 -5
- package/pages/Instance/Tabs/styles.ts +7 -7
- package/pages/Instance/index.tsx +0 -4
- package/pages/Instance/stores/Main.ts +29 -7
- package/pages/Snapshots/Snapshot/index.tsx +71 -0
- package/pages/Snapshots/Snapshot/stores/Main.ts +40 -0
- package/postgres.ai-shared-4.2.0.tgz +0 -0
- package/stores/Snapshots.ts +16 -7
- package/types/api/endpoints/getBranches.ts +3 -0
- package/types/api/endpoints/probeSource.ts +34 -0
- package/types/api/endpoints/testDbSource.ts +10 -1
- package/types/api/endpoints/updateBranch.ts +11 -0
- package/types/api/endpoints/updateSnapshot.ts +11 -0
- package/types/api/endpoints/upgradeClone.ts +8 -0
- package/types/api/entities/clone.ts +13 -1
- package/types/api/entities/config.ts +46 -0
- package/types/api/entities/instanceState.ts +6 -0
- package/types/api/entities/snapshot.ts +3 -0
- package/utils/api.ts +8 -0
- package/utils/clone.ts +7 -1
- package/utils/clonePoller.ts +47 -0
- package/utils/date.ts +19 -14
- package/utils/protection.ts +25 -0
- package/postgres.ai-shared-4.1.3.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,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,
|
|
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,
|
|
40
|
+
export const Configuration = observer(({ instanceId, switchActiveTab, reload, disableConfigModification, }) => {
|
|
41
|
+
var _a, _b;
|
|
39
42
|
const classes = useStyles();
|
|
40
43
|
const stores = useStores();
|
|
41
44
|
const { config, isConfigurationLoading, updateConfig, getSeImages, fullConfig, testDbSource, configError, getFullConfig, getFullConfigError, getEngine, } = stores.main;
|
|
42
45
|
const configData = config && JSON.parse(JSON.stringify(config));
|
|
43
|
-
const isConfigurationDisabled =
|
|
46
|
+
const isConfigurationDisabled = disableConfigModification;
|
|
44
47
|
const [dleEdition, setDledition] = useState('');
|
|
45
48
|
const isCeEdition = dleEdition === 'community';
|
|
46
49
|
const filteredDockerImageOptions = isCeEdition
|
|
47
50
|
? dockerImageOptions.filter((option) => option.type === 'custom' || option.type === 'Generic Postgres')
|
|
48
51
|
: dockerImageOptions;
|
|
52
|
+
const [userPickedMode, setUserPickedMode] = useState(null);
|
|
53
|
+
// Seeded lazily — only once configData transitions from null to non-null.
|
|
54
|
+
// Prevents the Simple/Expert default from flipping mid-session when an
|
|
55
|
+
// Apply triggers a refetch and the recomputed default would differ.
|
|
56
|
+
const [initialMode, setInitialMode] = useState(null);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (initialMode === null && configData) {
|
|
59
|
+
setInitialMode(getInitialConfigMode(configData.host, configData.retrievalMode));
|
|
60
|
+
}
|
|
61
|
+
}, [configData, initialMode]);
|
|
62
|
+
const configMode = (_a = userPickedMode !== null && userPickedMode !== void 0 ? userPickedMode : initialMode) !== null && _a !== void 0 ? _a : 'simple';
|
|
63
|
+
const setConfigMode = setUserPickedMode;
|
|
64
|
+
const portInitFromConfig = useRef(false);
|
|
49
65
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
50
66
|
const [submitState, setSubmitState] = useState({
|
|
51
67
|
status: '',
|
|
@@ -97,6 +113,8 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
|
|
|
97
113
|
});
|
|
98
114
|
await updateConfig({
|
|
99
115
|
...values,
|
|
116
|
+
// Preserve configs that originally had no port: key.
|
|
117
|
+
...(omitPortOnSubmit && { port: '' }),
|
|
100
118
|
tuningParams: formatTuningParamsToObj(values.tuningParams),
|
|
101
119
|
}, instanceId).then((response) => {
|
|
102
120
|
if (response === null || response === void 0 ? void 0 : response.ok) {
|
|
@@ -107,7 +125,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
|
|
|
107
125
|
}
|
|
108
126
|
});
|
|
109
127
|
};
|
|
110
|
-
const [{ formik, connectionData, isConnectionDataValid }] = useForm(onSubmit);
|
|
128
|
+
const [{ formik, connectionData, isConnectionDataValid, connectionString, connectionStringError, onConnectionStringChange, markPortInitialState, omitPortOnSubmit, },] = useForm(onSubmit);
|
|
111
129
|
// Memoized enhanced Docker images to avoid recreation on every render
|
|
112
130
|
// This combines predefined images with any custom image from configuration
|
|
113
131
|
const enhancedDockerImages = useMemo(() => {
|
|
@@ -413,6 +431,10 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
|
|
|
413
431
|
// Set initial data, empty string for password
|
|
414
432
|
useEffect(() => {
|
|
415
433
|
if (configData) {
|
|
434
|
+
if (!portInitFromConfig.current) {
|
|
435
|
+
markPortInitialState(configData.port == null || configData.port === '');
|
|
436
|
+
portInitFromConfig.current = true;
|
|
437
|
+
}
|
|
416
438
|
for (const [key, value] of Object.entries(configData)) {
|
|
417
439
|
if (key !== 'password') {
|
|
418
440
|
formik.setFieldValue(key, value);
|
|
@@ -494,91 +516,98 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
|
|
|
494
516
|
}, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, open: (isConfigurationDisabled || Boolean(dockerState.error)) &&
|
|
495
517
|
!isModalOpen, message: Boolean(dockerState.error)
|
|
496
518
|
? dockerState.error
|
|
497
|
-
:
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}, disabled:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
: testConnectionState.default.message.status
|
|
510
|
-
? testConnectionState.default.message.status
|
|
511
|
-
: '', message: testConnectionState.default.error ||
|
|
512
|
-
testConnectionState.default.message.message })) : null] }), _jsx(InputWithTooltip, { label: "pg_dump jobs", value: formik.values.dumpParallelJobs, tooltipText: tooltipText.dumpParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgDumpCustomOptions, label: "pg_dump customOptions", id: "pgDumpCustomOptions", tooltipText: tooltipText.pgDumpCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgDumpCustomOptions', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "dumpIgnoreErrors", checked: formik.values.dumpIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpIgnoreErrors', e.target.checked), classes: {
|
|
513
|
-
root: classes.checkboxRoot,
|
|
514
|
-
} }), label: 'Ignore errors during logical data dump' })] })] }), _jsxs(Box, { mb: 2, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "databaseContainer" }), _jsx("span", { className: classes.grayText, style: { margin: '0.5rem 0 1rem 0', display: 'block' }, children: "DBLab manages various database containers, such as clones. This section defines default container settings." }), _jsxs("div", { children: [_jsx(SelectWithTooltip, { label: "dockerImage - choose from the list *", value: formik.values.dockerImageType, error: Boolean(formik.errors.dockerImageType), tooltipText: tooltipText.dockerImageType, disabled: isConfigurationDisabled || dockerState.loading, items: filteredDockerImageOptions.map((image) => {
|
|
515
|
-
return {
|
|
516
|
-
value: image.type,
|
|
517
|
-
children: image.name,
|
|
518
|
-
};
|
|
519
|
-
}), onChange: handleDockerImageSelect }), formik.values.dockerImageType === 'custom' ? (_jsx(InputWithTooltip, { label: "dockerImage *", value: formik.values.dockerImage, error: formik.errors.dockerImage, tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled, onChange: (e) => {
|
|
520
|
-
formik.setValues({
|
|
521
|
-
...formik.values,
|
|
522
|
-
dockerImage: e.target.value,
|
|
523
|
-
dockerPath: e.target.value,
|
|
524
|
-
});
|
|
525
|
-
} })) : (_jsxs(_Fragment, { children: [_jsx(SelectWithTooltip, { label: "dockerImage - Postgres major version *", value: formik.values.dockerImage, error: Boolean(formik.errors.dockerImage), tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled ||
|
|
526
|
-
dockerState.loading ||
|
|
527
|
-
!dockerState.images.length, loading: dockerState.loading, items: dockerState.images
|
|
528
|
-
.slice()
|
|
529
|
-
.reverse()
|
|
530
|
-
.map((image) => {
|
|
531
|
-
return {
|
|
532
|
-
value: image,
|
|
533
|
-
children: image,
|
|
534
|
-
};
|
|
535
|
-
}), onChange: handleDockerVersionSelect }), _jsxs(Box, { mt: 0.5, mb: 2, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
|
|
519
|
+
: PREVENT_MODIFYING_MESSAGE, className: styles.snackbar }), !config && isConfigurationLoading ? (_jsx("div", { className: styles.spinnerContainer, children: _jsx(Spinner, { size: "lg", className: styles.spinner }) })) : (_jsxs(Box, { children: [_jsx(Header, { retrievalMode: formik.values.retrievalMode, setOpen: handleModalClick }), _jsxs(Tabs, { value: configMode, onChange: (_, value) => setConfigMode(value), indicatorColor: "primary", textColor: "primary", "aria-label": "Configuration mode", children: [_jsx(Tab, { value: "simple", label: "Simple" }), _jsx(Tab, { value: "expert", label: "Expert" })] }), configMode === 'simple' ? (_jsx(SimpleMode, { instanceId: instanceId, disabled: isConfigurationDisabled, onApplied: switchTab, onEdit: (proposed, password) => {
|
|
520
|
+
const projection = buildProjectionFromProposed(proposed, password);
|
|
521
|
+
formik.setValues({ ...formik.values, ...projection });
|
|
522
|
+
setConfigMode('expert');
|
|
523
|
+
} })) : null, configMode === 'expert' ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Box, { children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "debug", checked: formik.values.debug, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('debug', e.target.checked), classes: {
|
|
524
|
+
root: classes.checkboxRoot,
|
|
525
|
+
} }), label: 'Debug mode' }) }), _jsxs(Box, { mb: 1, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "retrieval" }), _jsxs(Box, { mt: 1, mb: 1, children: [_jsx(Typography, { className: styles.subsection, children: "Retrieval mode" }), _jsxs(RadioGroup, { row: true, "aria-label": "retrieval mode", value: formik.values.retrievalMode, onChange: (_, value) => {
|
|
526
|
+
formik.setFieldValue('retrievalMode', value);
|
|
527
|
+
}, children: [_jsx(FormControlLabel, { value: "logical", control: _jsx(Radio, { disabled: isConfigurationDisabled }), label: "Logical (dump/restore)" }), _jsx(FormControlLabel, { value: "physical", control: _jsx(Radio, { disabled: isConfigurationDisabled }), label: "Physical (WAL-G / pgBackRest)" })] })] }), formik.values.retrievalMode === 'physical' && (_jsx(PhysicalMode, { values: formik.values, onChange: (key, value) => formik.setFieldValue(key, value, key === 'physicalEnvs'), disabled: isConfigurationDisabled, envsKeyErrors: (_b = formik.errors.physicalEnvs) === null || _b === void 0 ? void 0 : _b.map((row) => row === null || row === void 0 ? void 0 : row.key) })), formik.values.retrievalMode === 'logical' && (_jsxs(Box, { mt: 1, children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalDump\"" }), _jsx("span", { className: classes.grayText, children: "Source database credentials and dumping options." }), _jsx(InputWithTooltip, { label: "Connection string *", value: connectionString, error: connectionStringError ||
|
|
528
|
+
formik.errors.host ||
|
|
529
|
+
formik.errors.username ||
|
|
530
|
+
formik.errors.dbname, tooltipText: () => (_jsxs(_Fragment, { children: ["URI form: ", _jsx("code", { children: "postgres://user@host:5432/dbname" }), ". DSN form is also accepted. Do not include the password here \u2014 use the Password field below."] })), disabled: isConfigurationDisabled, onChange: (e) => onConnectionStringChange(e.target.value) }), _jsx(Box, { mt: 0.5, mb: 1, children: _jsxs("span", { className: classes.grayText, "data-testid": "connection-string-parsed", children: ["host: ", formik.values.host || '—', " | port:", ' ', formik.values.port || '5432 (default)', " | user:", ' ', formik.values.username || '—', " | dbname:", ' ', formik.values.dbname || '—'] }) }), _jsx(InputWithTooltip, { type: "password", value: formik.values.password, label: "source.connection.password", tooltipText: tooltipText.password, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('password', e.target.value) }), _jsx(InputWithChip, { id: "databases", value: formik.values.databases, label: "Databases to copy", tooltipText: tooltipText.databases, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('databases', e.target.value) }), _jsxs(Box, { mt: 3, mb: 3, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
|
|
536
531
|
onTestConnectionClick({
|
|
537
|
-
type: '
|
|
532
|
+
type: 'default',
|
|
538
533
|
});
|
|
539
|
-
}, disabled: testConnectionState.
|
|
540
|
-
isConfigurationDisabled, children: ["
|
|
541
|
-
|
|
542
|
-
testConnectionState.dockerImage.message
|
|
534
|
+
}, disabled: testConnectionState.default.loading ||
|
|
535
|
+
isConfigurationDisabled, children: ["Test connection", testConnectionState.default.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.default.message.status ||
|
|
536
|
+
testConnectionState.default.error ? (_jsx(ResponseMessage, { type: testConnectionState.default.error
|
|
543
537
|
? 'error'
|
|
544
|
-
:
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
538
|
+
: testConnectionState.default.message.status
|
|
539
|
+
? testConnectionState.default.message.status
|
|
540
|
+
: '', message: testConnectionState.default.error ||
|
|
541
|
+
testConnectionState.default.message.message })) : null] }), _jsx(InputWithTooltip, { label: "pg_dump jobs", value: formik.values.dumpParallelJobs, tooltipText: tooltipText.dumpParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgDumpCustomOptions, label: "pg_dump customOptions", id: "pgDumpCustomOptions", tooltipText: tooltipText.pgDumpCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgDumpCustomOptions', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "dumpIgnoreErrors", checked: formik.values.dumpIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('dumpIgnoreErrors', e.target.checked), classes: {
|
|
542
|
+
root: classes.checkboxRoot,
|
|
543
|
+
} }), label: 'Ignore errors during logical data dump' })] }))] }), _jsxs(Box, { mb: 2, mt: 1, children: [_jsx(ConfigSectionTitle, { tag: "databaseContainer" }), _jsx("span", { className: classes.grayText, style: { margin: '0.5rem 0 1rem 0', display: 'block' }, children: "DBLab manages various database containers, such as clones. This section defines default container settings." }), _jsxs("div", { children: [_jsx(SelectWithTooltip, { label: "dockerImage - choose from the list *", value: formik.values.dockerImageType, error: Boolean(formik.errors.dockerImageType), tooltipText: tooltipText.dockerImageType, disabled: isConfigurationDisabled || dockerState.loading, items: filteredDockerImageOptions.map((image) => {
|
|
544
|
+
return {
|
|
545
|
+
value: image.type,
|
|
546
|
+
children: image.name,
|
|
547
|
+
};
|
|
548
|
+
}), onChange: handleDockerImageSelect }), formik.values.dockerImageType === 'custom' ? (_jsx(InputWithTooltip, { label: "dockerImage *", value: formik.values.dockerImage, error: formik.errors.dockerImage, tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled, onChange: (e) => {
|
|
550
549
|
formik.setValues({
|
|
551
550
|
...formik.values,
|
|
552
|
-
|
|
553
|
-
dockerPath:
|
|
551
|
+
dockerImage: e.target.value,
|
|
552
|
+
dockerPath: e.target.value,
|
|
554
553
|
});
|
|
555
|
-
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
554
|
+
} })) : (_jsxs(_Fragment, { children: [_jsx(SelectWithTooltip, { label: "dockerImage - Postgres major version *", value: formik.values.dockerImage, error: Boolean(formik.errors.dockerImage), tooltipText: tooltipText.dockerImage, disabled: isConfigurationDisabled ||
|
|
555
|
+
dockerState.loading ||
|
|
556
|
+
!dockerState.images.length, loading: dockerState.loading, items: dockerState.images
|
|
557
|
+
.slice()
|
|
558
|
+
.reverse()
|
|
559
|
+
.map((image) => {
|
|
560
|
+
return {
|
|
561
|
+
value: image,
|
|
562
|
+
children: image,
|
|
563
|
+
};
|
|
564
|
+
}), onChange: handleDockerVersionSelect }), _jsxs(Box, { mt: 0.5, mb: 2, children: [_jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
|
|
565
|
+
onTestConnectionClick({
|
|
566
|
+
type: 'dockerImage',
|
|
567
|
+
});
|
|
568
|
+
}, disabled: testConnectionState.dockerImage.loading ||
|
|
569
|
+
isConfigurationDisabled, children: ["Get version from source", testConnectionState.dockerImage.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.dockerImage.message.status ===
|
|
570
|
+
'error' || testConnectionState.dockerImage.error ? (_jsx(ResponseMessage, { type: testConnectionState.dockerImage.error ||
|
|
571
|
+
testConnectionState.dockerImage.message
|
|
572
|
+
? 'error'
|
|
573
|
+
: '', message: testConnectionState.dockerImage.error ||
|
|
574
|
+
testConnectionState.dockerImage.message.message })) : null] }), _jsx(SelectWithTooltip, { label: "dockerImage - tag *", value: formik.values.dockerTag, error: Boolean(formik.errors.dockerTag), tooltipText: tooltipText.dockerTag, disabled: isConfigurationDisabled ||
|
|
575
|
+
dockerState.loading ||
|
|
576
|
+
!dockerState.tags.length, loading: dockerState.loading, onChange: (e) => {
|
|
577
|
+
var _a;
|
|
578
|
+
const currentLocation = (_a = dockerState.data.find((image) => image.tag === e.target.value)) === null || _a === void 0 ? void 0 : _a.location;
|
|
579
|
+
formik.setValues({
|
|
580
|
+
...formik.values,
|
|
581
|
+
dockerTag: e.target.value,
|
|
582
|
+
dockerPath: currentLocation,
|
|
583
|
+
});
|
|
584
|
+
}, items: dockerState.tags.map((image) => {
|
|
585
|
+
return {
|
|
586
|
+
value: image,
|
|
587
|
+
children: image,
|
|
588
|
+
};
|
|
589
|
+
}) })] })), _jsxs(Typography, { paragraph: true, children: ["Cannot find your image? Reach out to support:", ' ', _jsxs("a", { href: 'https://postgres.ai/contact', target: "_blank", className: styles.externalLink, children: ["https://postgres.ai/contact", _jsx(ExternalIcon, { className: styles.externalIcon })] })] })] })] }), _jsxs(Box, { mb: 3, children: [_jsx(ConfigSectionTitle, { tag: "databaseConfigs" }), _jsx("span", { className: classes.grayText, style: { marginTop: '0.5rem', display: 'block' }, children: "Default PostgreSQL configuration used for all PostgreSQL instances running in containers managed by DBLab." }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_buffers parameter", value: formik.values.sharedBuffers, tooltipText: tooltipText.sharedBuffers, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedBuffers', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "shared_preload_libraries", value: formik.values.sharedPreloadLibraries, tooltipText: tooltipText.sharedPreloadLibraries, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('sharedPreloadLibraries', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Query tuning parameters", value: typeof formik.values.tuningParams === 'object'
|
|
590
|
+
? Object.entries(formik.values.tuningParams)
|
|
591
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
592
|
+
.join('\n')
|
|
593
|
+
: formik.values.tuningParams, tooltipText: tooltipText.tuningParams, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('tuningParams', e.target.value) }), _jsxs(Button, { variant: "outlined", color: "secondary", onClick: () => {
|
|
594
|
+
onTestConnectionClick({
|
|
595
|
+
type: 'fetchTuning',
|
|
596
|
+
});
|
|
597
|
+
}, disabled: testConnectionState.fetchTuning.loading ||
|
|
598
|
+
isConfigurationDisabled, children: ["Get from source database", testConnectionState.fetchTuning.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.fetchTuning.message.status === 'error' ||
|
|
599
|
+
testConnectionState.fetchTuning.error ? (_jsx(ResponseMessage, { type: testConnectionState.fetchTuning.error ||
|
|
600
|
+
testConnectionState.fetchTuning.message
|
|
601
|
+
? 'error'
|
|
602
|
+
: '', message: testConnectionState.fetchTuning.error ||
|
|
603
|
+
testConnectionState.fetchTuning.message.message })) : null] }), formik.values.retrievalMode === 'logical' && (_jsxs(Box, { children: [_jsxs(Box, { children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalRestore\"" }), _jsx("span", { className: classes.grayText, children: "Restoring options." })] }), _jsx(InputWithTooltip, { label: "pg_restore jobs", value: formik.values.restoreParallelJobs, tooltipText: tooltipText.restoreParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgRestoreCustomOptions, label: "pg_restore customOptions", id: "pgRestoreCustomOptions", tooltipText: tooltipText.pgRestoreCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgRestoreCustomOptions', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Restore PostgreSQL configs", value: formik.values.restoreConfigs, tooltipText: tooltipText.restoreConfigs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreConfigs', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "restoreIgnoreErrors", checked: formik.values.restoreIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreIgnoreErrors', e.target.checked), classes: {
|
|
604
|
+
root: classes.checkboxRoot,
|
|
605
|
+
} }), label: 'Ignore errors during logical data restore' })] })), _jsx(Box, { mt: 1, children: _jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.refresh\"" }) }), _jsxs("span", { className: classes.grayText, children: ["Define full data refresh on schedule. The process requires at least one additional filesystem mount point. The schedule is to be specified using", ' ', _jsxs("a", { href: "https://en.wikipedia.org/wiki/Cron#Overview", target: "_blank", className: styles.externalLink, children: ["crontab format", _jsx(ExternalIcon, { className: styles.externalIcon })] }), "."] }), _jsx(InputWithTooltip, { label: "timetable", value: formik.values.timetable, tooltipText: tooltipText.timetable, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('timetable', e.target.value) })] }), _jsxs(Box, { mt: 2, mb: 2, sx: {
|
|
606
|
+
display: 'flex',
|
|
607
|
+
alignItems: 'center',
|
|
608
|
+
}, children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
609
|
+
formik.submitForm().then(() => {
|
|
610
|
+
scrollToField();
|
|
567
611
|
});
|
|
568
|
-
}, disabled:
|
|
569
|
-
isConfigurationDisabled, children: ["Get from source database", testConnectionState.fetchTuning.loading && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), testConnectionState.fetchTuning.message.status === 'error' ||
|
|
570
|
-
testConnectionState.fetchTuning.error ? (_jsx(ResponseMessage, { type: testConnectionState.fetchTuning.error ||
|
|
571
|
-
testConnectionState.fetchTuning.message
|
|
572
|
-
? 'error'
|
|
573
|
-
: '', message: testConnectionState.fetchTuning.error ||
|
|
574
|
-
testConnectionState.fetchTuning.message.message })) : null] }), _jsxs(Box, { children: [_jsxs(Box, { children: [_jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.spec.logicalRestore\"" }), _jsx("span", { className: classes.grayText, children: "Restoring options." })] }), _jsx(InputWithTooltip, { label: "pg_restore jobs", value: formik.values.restoreParallelJobs, tooltipText: tooltipText.restoreParallelJobs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreParallelJobs', e.target.value) }), _jsx(InputWithChip, { value: formik.values.pgRestoreCustomOptions, label: "pg_restore customOptions", id: "pgRestoreCustomOptions", tooltipText: tooltipText.pgRestoreCustomOptions, handleDeleteChip: handleDeleteChip, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('pgRestoreCustomOptions', e.target.value) }), _jsx(InputWithTooltip, { type: "textarea", label: "Restore PostgreSQL configs", value: formik.values.restoreConfigs, tooltipText: tooltipText.restoreConfigs, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreConfigs', e.target.value) }), _jsx(FormControlLabel, { style: { maxWidth: 'max-content' }, control: _jsx(Checkbox, { name: "restoreIgnoreErrors", checked: formik.values.restoreIgnoreErrors, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('restoreIgnoreErrors', e.target.checked), classes: {
|
|
575
|
-
root: classes.checkboxRoot,
|
|
576
|
-
} }), label: 'Ignore errors during logical data restore' })] }), _jsx(Box, { mt: 1, children: _jsx(Typography, { className: styles.subsection, children: "Subsection \"retrieval.refresh\"" }) }), _jsxs("span", { className: classes.grayText, children: ["Define full data refresh on schedule. The process requires at least one additional filesystem mount point. The schedule is to be specified using", ' ', _jsxs("a", { href: "https://en.wikipedia.org/wiki/Cron#Overview", target: "_blank", className: styles.externalLink, children: ["crontab format", _jsx(ExternalIcon, { className: styles.externalIcon })] }), "."] }), _jsx(InputWithTooltip, { label: "timetable", value: formik.values.timetable, tooltipText: tooltipText.timetable, disabled: isConfigurationDisabled, onChange: (e) => formik.setFieldValue('timetable', e.target.value) })] }), _jsxs(Box, { mt: 2, mb: 2, sx: {
|
|
577
|
-
display: 'flex',
|
|
578
|
-
alignItems: 'center',
|
|
579
|
-
}, children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
580
|
-
formik.submitForm().then(() => {
|
|
581
|
-
scrollToField();
|
|
582
|
-
});
|
|
583
|
-
}, disabled: formik.isSubmitting || isConfigurationDisabled, children: ["Apply changes", formik.isSubmitting && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), _jsx(Box, { sx: { px: 2 }, children: _jsx(Button, { variant: "outlined", color: "secondary", onClick: switchTab, children: "Cancel" }) })] }), (submitState.status && submitState.response) || configError ? (_jsx(ResponseMessage, { type: configError ? 'error' : submitState.status, message: configError || submitState.response })) : null] })), _jsx(Modal, { title: _jsx(ModalTitle, {}), onClose: () => setIsModalOpen(false), isOpen: isModalOpen, size: "xl", children: _jsx(Editor, { height: "70vh", width: "100%", defaultLanguage: "yaml", value: getFullConfigError ? getFullConfigError : fullConfig, loading: _jsx(StubSpinner, {}), theme: "vs-light", options: { domReadOnly: true, readOnly: true } }) })] }));
|
|
612
|
+
}, disabled: formik.isSubmitting || isConfigurationDisabled, children: ["Apply changes", formik.isSubmitting && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }), _jsx(Box, { sx: { px: 2 }, children: _jsx(Button, { variant: "outlined", color: "secondary", onClick: switchTab, children: "Cancel" }) })] }), (submitState.status && submitState.response) || configError ? (_jsx(ResponseMessage, { type: configError ? 'error' : submitState.status, message: configError || submitState.response })) : null] })) : null] })), _jsx(Modal, { title: _jsx(ModalTitle, {}), onClose: () => setIsModalOpen(false), isOpen: isModalOpen, size: "xl", children: _jsx(Editor, { height: "70vh", width: "100%", defaultLanguage: "yaml", value: getFullConfigError ? getFullConfigError : fullConfig, loading: _jsx(StubSpinner, {}), theme: "vs-light", options: { domReadOnly: true, readOnly: true } }) })] }));
|
|
584
613
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
export declare type RetrievalMode = 'logical' | 'physical';
|
|
3
|
+
export declare type PhysicalTool = 'walg' | 'pgbackrest' | 'customTool' | '';
|
|
4
|
+
export declare type PhysicalEnv = {
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
2
8
|
export declare type FormValues = {
|
|
3
9
|
debug: boolean;
|
|
4
10
|
dockerImage: string;
|
|
@@ -22,6 +28,14 @@ export declare type FormValues = {
|
|
|
22
28
|
restoreConfigs: string;
|
|
23
29
|
pgDumpCustomOptions: string;
|
|
24
30
|
pgRestoreCustomOptions: string;
|
|
31
|
+
retrievalMode: RetrievalMode;
|
|
32
|
+
physicalTool: PhysicalTool;
|
|
33
|
+
physicalDockerImage: string;
|
|
34
|
+
physicalSyncEnabled: boolean;
|
|
35
|
+
physicalWalgBackupName: string;
|
|
36
|
+
physicalPgbackrestStanza: string;
|
|
37
|
+
physicalPgbackrestDelta: boolean;
|
|
38
|
+
physicalEnvs: PhysicalEnv[];
|
|
25
39
|
};
|
|
26
40
|
export declare const useForm: (onSubmit: (values: FormValues) => void) => {
|
|
27
41
|
formik: {
|
|
@@ -80,4 +94,10 @@ export declare const useForm: (onSubmit: (values: FormValues) => void) => {
|
|
|
80
94
|
dbname: string;
|
|
81
95
|
};
|
|
82
96
|
isConnectionDataValid: string;
|
|
97
|
+
connectionString: string;
|
|
98
|
+
connectionStringError: string | null;
|
|
99
|
+
onConnectionStringChange: (s: string) => void;
|
|
100
|
+
markPortInitialState: (wasUnset: boolean) => void;
|
|
101
|
+
markPortDirty: () => void;
|
|
102
|
+
omitPortOnSubmit: boolean;
|
|
83
103
|
}[];
|