@postgres.ai/shared 4.2.0-pr-1203 → 4.2.0-pr-1201.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Button/index.js +0 -1
- package/components/Button2/index.js +0 -1
- package/components/Status/index.js +0 -1
- package/components/TextField/index.js +1 -1
- package/config/index.js +1 -1
- package/helpers/getEntropy.js +2 -2
- package/helpers/request.js +1 -1
- package/icons/ArrowDropDown/index.js +3 -1
- package/icons/Renewable/index.js +0 -1
- package/icons/Shield/index.js +0 -1
- package/icons/Warning/index.js +3 -1
- package/package.json +1 -1
- package/pages/Branches/index.js +2 -4
- package/pages/Clone/Status/index.js +0 -1
- package/pages/Clone/index.js +3 -3
- package/pages/CreateClone/index.js +1 -1
- package/pages/CreateClone/utils/index.js +1 -1
- package/pages/Instance/Configuration/Header/index.js +2 -2
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +0 -2
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +1 -8
- package/pages/Instance/Configuration/index.js +15 -22
- package/pages/Instance/Configuration/tooltipText.js +4 -4
- package/pages/Instance/Configuration/useForm.js +1 -1
- package/pages/Instance/Configuration/utils/index.d.ts +1 -1
- package/pages/Instance/Configuration/utils/index.js +16 -16
- package/pages/Instance/Tabs/PlatformTabs.d.ts +2 -2
- package/pages/Instance/Tabs/PlatformTabs.js +1 -1
- package/pages/Instance/Tabs/index.d.ts +1 -1
- package/pages/Instance/index.js +2 -2
- package/pages/Instance/stores/Main.js +5 -5
- package/pages/Logs/hooks/useWsScroll.js +14 -22
- package/pages/Logs/index.js +14 -11
- package/pages/Logs/utils/index.js +1 -1
- package/pages/Logs/wsLogs.d.ts +0 -1
- package/pages/Logs/wsLogs.js +1 -25
- package/pages/Snapshots/Snapshot/stores/Main.js +1 -2
- package/styles/icons.js +1 -1
- package/styles/styles.js +0 -3
- package/styles/theme.js +2 -1
- package/types/api/endpoints/getFullConfig.d.ts +1 -1
- package/types/api/endpoints/testDbSource.js +2 -2
- package/types/api/entities/clone.d.ts +0 -9
- package/types/api/entities/clone.js +0 -1
- package/types/api/entities/config.d.ts +1 -1
- package/types/api/entities/instance.d.ts +0 -5
- package/types/api/entities/instanceRetrieval.d.ts +2 -2
- package/types/api/entities/instanceState.d.ts +0 -5
- package/utils/api.js +2 -2
- package/components/ErrorBoundary/index.d.ts +0 -15
- package/components/ErrorBoundary/index.js +0 -24
|
@@ -38,4 +38,3 @@ export const Button = forwardRef((props, ref) => {
|
|
|
38
38
|
const classes = useStyles();
|
|
39
39
|
return (_jsx(ButtonBase, { ...buttonProps, size: size, ref: ref, disabled: isDisabled, className: clsx(classes.root, className), variant: VARIANT_MAP[variant], color: "primary" }));
|
|
40
40
|
});
|
|
41
|
-
Button.displayName = 'Button';
|
|
@@ -8,4 +8,3 @@ export const Button = React.forwardRef((props, ref) => {
|
|
|
8
8
|
const isDisabled = props.isDisabled || props.isLoading;
|
|
9
9
|
return (_jsxs("button", { ref: ref, className: cn(styles.root, styles[size], styles[theme], props.className), type: type, onClick: props.onClick, disabled: isDisabled, children: [props.children, props.isLoading && _jsx(Spinner, { size: "sm", className: styles.spinner })] }));
|
|
10
10
|
});
|
|
11
|
-
Button.displayName = 'Button2';
|
|
@@ -21,4 +21,3 @@ export const Status = React.forwardRef((props, ref) => {
|
|
|
21
21
|
const { type = 'ok', children = type, icon = TYPE_TO_ICON[type], className, classNameIcon, disableColor, ...hiddenProps } = props;
|
|
22
22
|
return (_jsxs("span", { ...hiddenProps, className: clsx(styles.root, !disableColor && styles[type], className), ref: ref, children: [icon && (_jsxs("span", { className: clsx(styles.iconContainer, classNameIcon), children: [icon, "\u2009"] })), children] }));
|
|
23
23
|
});
|
|
24
|
-
Status.displayName = 'Status';
|
|
@@ -39,5 +39,5 @@ export const TextField = (props) => {
|
|
|
39
39
|
classes: {
|
|
40
40
|
root: classes.helperText
|
|
41
41
|
}
|
|
42
|
-
}, onChange: props.onChange, select: props.select, type: props.type, error: props.error, placeholder: props.placeholder, onBlur: props.onBlur, onFocus: props.onFocus, name: props.name, helperText: props.helperText, style: props.style
|
|
42
|
+
}, onChange: props.onChange, children: props.children, select: props.select, type: props.type, error: props.error, placeholder: props.placeholder, onBlur: props.onBlur, onFocus: props.onFocus, name: props.name, helperText: props.helperText, style: props.style }));
|
|
43
43
|
};
|
package/config/index.js
CHANGED
package/helpers/getEntropy.js
CHANGED
|
@@ -6,7 +6,7 @@ const upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
6
6
|
const digitsChars = '0123456789';
|
|
7
7
|
export const MIN_ENTROPY = 60;
|
|
8
8
|
function getBase(password) {
|
|
9
|
-
|
|
9
|
+
let uniqueChars = [];
|
|
10
10
|
for (const c of password) {
|
|
11
11
|
if (!uniqueChars.includes(c)) {
|
|
12
12
|
uniqueChars.push(c);
|
|
@@ -71,7 +71,7 @@ const seqKeyboard2 = 'zxcvbnm';
|
|
|
71
71
|
const seqAlphabet = 'abcdefghijklmnopqrstuvwxyz';
|
|
72
72
|
function removeMoreThanTwoFromSequence(s, seq) {
|
|
73
73
|
const seqRunes = Array.from(seq);
|
|
74
|
-
|
|
74
|
+
let runes = Array.from(s);
|
|
75
75
|
let matches = 0;
|
|
76
76
|
for (let i = 0; i < runes.length; i++) {
|
|
77
77
|
for (let j = 0; j < seqRunes.length; j++) {
|
package/helpers/request.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
export const ArrowDropDownIcon = (props) => {
|
|
3
|
-
return (_jsx("svg", { className: props.className, viewBox: "0 0 8 6", fill: "none", xmlns: "http://www.w3.org/2000/svg", onClick: props.onClick, children: _jsx("path", {
|
|
3
|
+
return (_jsx("svg", { className: props.className, viewBox: "0 0 8 6", fill: "none", xmlns: "http://www.w3.org/2000/svg", onClick: props.onClick, children: _jsx("path", {
|
|
4
|
+
// eslint-disable-next-line max-len
|
|
5
|
+
d: "M7.8515 0.898419C7.75261 0.799446 7.63538 0.75 7.49994 0.75H0.500038C0.364534 0.75 0.247392 0.799446 0.148419 0.898419C0.0494455 0.997501 0 1.11464 0 1.25006C0 1.38546 0.0494455 1.5026 0.148419 1.6016L3.64838 5.10156C3.74746 5.20054 3.86461 5.25009 4 5.25009C4.13539 5.25009 4.25265 5.20054 4.35154 5.10156L7.8515 1.60157C7.95036 1.5026 8 1.38546 8 1.25004C8 1.11464 7.95036 0.997501 7.8515 0.898419Z", fill: "currentColor" }) }));
|
|
4
6
|
};
|
package/icons/Renewable/index.js
CHANGED
|
@@ -10,4 +10,3 @@ export const RenewableIcon = React.forwardRef((props, ref) => {
|
|
|
10
10
|
const { className, ...hiddenProps } = props;
|
|
11
11
|
return (_jsxs("svg", { ...hiddenProps, ref: ref, className: className, viewBox: "0 0 10 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.14337 0L8.19809 2.9755L4.59387 3.26718L5.16139 2.07055C4.22847 2.10806 3.3161 2.48386 2.66614 3.18795L2.20594 2.76312C3.05679 1.84141 4.26977 1.40592 5.45883 1.44339L6.14337 0Z", fill: "currentColor" }), _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M9.7343 9.13905L6.12728 9.39371L7.71025 6.14261L8.45158 7.24009C8.89401 6.4179 9.03479 5.44126 8.75942 4.52346L9.35931 4.34347C9.71979 5.54495 9.47743 6.81074 8.8401 7.81527L9.7343 9.13905Z", fill: "currentColor" }), _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M0 7.57495L1.61898 4.34163L3.60962 7.36037L2.28757 7.43896C2.76953 8.23862 3.53811 8.85742 4.46813 9.08819L4.3173 9.69607C3.09983 9.39397 2.13422 8.54041 1.59468 7.48015L0 7.57495Z", fill: "currentColor" }), _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M6.26612 2.51078C5.07868 2.17637 3.73747 2.48899 2.89616 3.40036L1.97575 2.55072C3.17071 1.25624 5.01514 0.857134 6.60567 1.30506L6.26612 2.51078Z", fill: "currentColor" }), _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M7.33938 8.11969C8.34588 7.28131 8.83847 5.87691 8.45939 4.61345L9.65917 4.25348C10.1969 6.04581 9.49312 7.95595 8.14107 9.08215L7.33938 8.11969Z", fill: "currentColor" }), _jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M2.07072 5.82736C2.20416 7.20026 3.19328 8.44922 4.54348 8.78425L4.24181 10C2.32911 9.5254 1.00387 7.79934 0.823976 5.94853L2.07072 5.82736Z", fill: "currentColor" })] }));
|
|
12
12
|
});
|
|
13
|
-
RenewableIcon.displayName = 'RenewableIcon';
|
package/icons/Shield/index.js
CHANGED
|
@@ -10,4 +10,3 @@ export const ShieldIcon = React.forwardRef((props, ref) => {
|
|
|
10
10
|
const { className, ...hiddenProps } = props;
|
|
11
11
|
return (_jsx("svg", { ...hiddenProps, ref: ref, className: className, viewBox: "0 0 8 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M4.00195 0L0.251953 1.66667V4.16667C0.251953 6.47917 1.85195 8.64167 4.00195 9.16667C6.15195 8.64167 7.75195 6.47917 7.75195 4.16667V1.66667L4.00195 0ZM3.16862 6.66667L1.50195 5L2.08945 4.4125L3.16862 5.4875L5.91445 2.74167L6.50195 3.33333L3.16862 6.66667Z", fill: "currentColor" }) }));
|
|
12
12
|
});
|
|
13
|
-
ShieldIcon.displayName = 'ShieldIcon';
|
package/icons/Warning/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
export const WarningIcon = (props) => {
|
|
3
|
-
return (_jsx("svg", { className: props.className, viewBox: "0 0 10 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", {
|
|
3
|
+
return (_jsx("svg", { className: props.className, viewBox: "0 0 10 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", {
|
|
4
|
+
// eslint-disable-next-line max-len
|
|
5
|
+
d: "M9.79222 7.49885L6.2597 1.00518C5.69214 0.04969 4.30861 0.0484205 3.74029 1.00518L0.207947 7.49885C-0.372248 8.4752 0.330193 9.71157 1.46736 9.71157H8.53251C9.66873 9.71157 10.3724 8.47619 9.79222 7.49885ZM5 8.53969C4.67699 8.53969 4.41406 8.27676 4.41406 7.95375C4.41406 7.63074 4.67699 7.36782 5 7.36782C5.323 7.36782 5.58593 7.63074 5.58593 7.95375C5.58593 8.27676 5.323 8.53969 5 8.53969ZM5.58593 6.19594C5.58593 6.51895 5.323 6.78188 5 6.78188C4.67699 6.78188 4.41406 6.51895 4.41406 6.19594V3.26625C4.41406 2.94324 4.67699 2.68032 5 2.68032C5.323 2.68032 5.58593 2.94324 5.58593 3.26625V6.19594Z", fill: "currentColor" }) }));
|
|
4
6
|
};
|
package/package.json
CHANGED
package/pages/Branches/index.js
CHANGED
|
@@ -48,8 +48,7 @@ export const Branches = observer(({ instanceId }) => {
|
|
|
48
48
|
const loadBranches = () => {
|
|
49
49
|
getBranches(instanceId)
|
|
50
50
|
.then((response) => {
|
|
51
|
-
|
|
52
|
-
setBranches(response);
|
|
51
|
+
response && setBranches(response);
|
|
53
52
|
})
|
|
54
53
|
.finally(() => setIsLoading(false));
|
|
55
54
|
};
|
|
@@ -57,8 +56,7 @@ export const Branches = observer(({ instanceId }) => {
|
|
|
57
56
|
loadBranches();
|
|
58
57
|
const intervalId = setInterval(() => {
|
|
59
58
|
getBranches(instanceId, true).then((response) => {
|
|
60
|
-
|
|
61
|
-
setBranches(response);
|
|
59
|
+
response && setBranches(response);
|
|
62
60
|
});
|
|
63
61
|
}, listRefreshIntervalMs);
|
|
64
62
|
return () => clearInterval(intervalId);
|
|
@@ -39,4 +39,3 @@ export const Status = React.memo((props) => {
|
|
|
39
39
|
const isError = statusType === 'error';
|
|
40
40
|
return (_jsxs("div", { className: clsx(classes.root, className), children: [_jsx(StatusBase, { type: statusType, className: classes.status, children: statusText }), !isError && _jsx("p", { className: classes.message, children: message }), isError && (_jsx(FormattedText, { value: message, className: classes.errorMessage }))] }));
|
|
41
41
|
});
|
|
42
|
-
Status.displayName = 'CloneStatus';
|
package/pages/Clone/index.js
CHANGED
|
@@ -274,21 +274,21 @@ export const Clone = observer((props) => {
|
|
|
274
274
|
: '-'] }), _jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Clone creation time:" }), clone.metadata.cloningTime
|
|
275
275
|
? `${round(clone.metadata.cloningTime, 2)} s`
|
|
276
276
|
: '-'] })] }), _jsx("br", {}), hasConnectionInfo && (_jsxs(_Fragment, { children: [_jsx("p", { children: _jsx("strong", { children: "Connection info" }) }), sshPortForwardingUrl && (_jsxs("div", { className: classes.fieldBlock, children: ["In a separate console, set up SSH port forwarding (and keep it running):", _jsxs("div", { className: classes.copyFieldContainer, children: [_jsx(TextField, { variant: "outlined", label: "SSH port forwarding", value: sshPortForwardingUrl, className: classes.textField, margin: "normal", fullWidth: true,
|
|
277
|
-
// @ts-
|
|
277
|
+
// @ts-ignore
|
|
278
278
|
readOnly: true, InputLabelProps: {
|
|
279
279
|
shrink: true,
|
|
280
280
|
style: styles.inputFieldLabel,
|
|
281
281
|
}, FormHelperTextProps: {
|
|
282
282
|
style: styles.inputFieldHelper,
|
|
283
283
|
} }), _jsx(IconButton, { className: classes.copyButton, "aria-label": "Copy", onClick: () => copyToClipboard(sshPortForwardingUrl), children: icons.copyIcon })] })] })), psqlConnectionStr && (_jsxs("div", { className: classes.fieldBlock, children: [_jsxs("div", { className: classes.copyFieldContainer, children: [_jsx(TextField, { variant: "outlined", id: "psqlConnStr", label: "psql connection string", value: psqlConnectionStr, className: classes.textField, margin: "normal", fullWidth: true,
|
|
284
|
-
// @ts-
|
|
284
|
+
// @ts-ignore
|
|
285
285
|
readOnly: true, InputLabelProps: {
|
|
286
286
|
shrink: true,
|
|
287
287
|
style: styles.inputFieldLabel,
|
|
288
288
|
}, FormHelperTextProps: {
|
|
289
289
|
style: styles.inputFieldHelper,
|
|
290
290
|
} }), _jsx(IconButton, { className: classes.copyButton, "aria-label": "Copy", onClick: () => copyToClipboard(psqlConnectionStr), children: icons.copyIcon })] }), "\u00A0", _jsx(Tooltip, { content: _jsx(_Fragment, { children: "Used to connect to PostgreSQL using psql. Change DBNAME to the name of the database you want to connect to. Use the PGPASSWORD environment variable to set the database password or type it when prompted." }), children: _jsx("span", { className: classes.textFieldInfo, children: icons.infoIcon }) })] })), jdbcConnectionStr && (_jsxs("div", { className: classes.fieldBlock, children: [_jsxs("div", { className: classes.copyFieldContainer, children: [_jsx(TextField, { variant: "outlined", label: "JDBC connection string", value: jdbcConnectionStr, className: classes.textField, margin: "normal", fullWidth: true,
|
|
291
|
-
// @ts-
|
|
291
|
+
// @ts-ignore
|
|
292
292
|
readOnly: true, InputLabelProps: {
|
|
293
293
|
shrink: true,
|
|
294
294
|
style: styles.inputFieldLabel,
|
|
@@ -96,7 +96,7 @@ export const CreateClone = observer((props) => {
|
|
|
96
96
|
const allSnapshots = (_f = (_e = (_d = stores.main) === null || _d === void 0 ? void 0 : _d.snapshots) === null || _e === void 0 ? void 0 : _e.data) !== null && _f !== void 0 ? _f : [];
|
|
97
97
|
const sortedSnapshots = allSnapshots.slice().sort(compareSnapshotsDesc);
|
|
98
98
|
setSnapshots(sortedSnapshots);
|
|
99
|
-
|
|
99
|
+
let selectedSnapshot = allSnapshots.find(s => s.id === initialSnapshotId) || allSnapshots[0];
|
|
100
100
|
formik.setFieldValue('snapshotId', selectedSnapshot === null || selectedSnapshot === void 0 ? void 0 : selectedSnapshot.id);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -21,7 +21,7 @@ export const getCliCreateCloneCommand = (values, showPassword) => {
|
|
|
21
21
|
|
|
22
22
|
${protectedFlag} \
|
|
23
23
|
|
|
24
|
-
--id ${cloneIdDisplay}`;
|
|
24
|
+
--id ${cloneIdDisplay} \ `;
|
|
25
25
|
};
|
|
26
26
|
export const getCliCloneStatus = (cloneId) => {
|
|
27
27
|
const cloneIdDisplay = cloneId ? shellEscape(cloneId) : `<CLONE_ID>`;
|
|
@@ -13,5 +13,5 @@ import { ExternalIcon } from '@postgres.ai/shared/icons/External';
|
|
|
13
13
|
import styles from '../styles.module.scss';
|
|
14
14
|
export const ConfigSectionTitle = ({ tag }) => (_jsx(SectionTitle, { level: 2, tag: "h2", text: _jsxs("div", { className: styles.sectionTitle, children: [_jsx("p", { children: "Section" }), _jsxs("p", { children: ["\"", tag, "\""] })] }) }));
|
|
15
15
|
const DOCS_URL = 'https://postgres.ai/docs/reference-guides/database-lab-engine-configuration-reference';
|
|
16
|
-
export const Header = (props) => (_jsxs("div", { className: styles.root, children: [_jsxs(Box, { mb: 3, children: [_jsx(Typography, { paragraph: true, children: "Only select parameters can be changed here." }), _jsxs(Typography, { paragraph: true, children: ["However, you can still see", ' ', _jsx(Link, { href: "#", underline: "always", onClick: props.setOpen, className: styles.externalLink, children: "the full config" }), ". For details, read", ' ', _jsxs("a", { href: DOCS_URL, target: "_blank",
|
|
17
|
-
export const ModalTitle = () => (_jsxs("div", { children: [_jsx(Typography, { className: styles.modalTitle, children: "Full configuration file (view only)" }), _jsxs(Typography, { variant: "h3", children: ["Sensitive values are masked. For details, read", ' ', _jsxs("a", { href: DOCS_URL, target: "_blank",
|
|
16
|
+
export const Header = (props) => (_jsxs("div", { className: styles.root, children: [_jsxs(Box, { mb: 3, children: [_jsx(Typography, { paragraph: true, children: "Only select parameters can be changed here." }), _jsxs(Typography, { paragraph: true, children: ["However, you can still see", ' ', _jsx(Link, { href: "#", underline: "always", onClick: props.setOpen, className: styles.externalLink, children: "the full config" }), ". For details, read", ' ', _jsxs("a", { href: DOCS_URL, target: "_blank", className: styles.externalLink, children: ["the docs", _jsx(ExternalIcon, { className: styles.externalIcon })] }), "."] }), _jsxs(Typography, { paragraph: true, children: [_jsx("strong", { children: "Data retrieval mode" }), ": ", props.retrievalMode] })] }), _jsx(ConfigSectionTitle, { tag: "global" })] }));
|
|
17
|
+
export const ModalTitle = () => (_jsxs("div", { children: [_jsx(Typography, { className: styles.modalTitle, children: "Full configuration file (view only)" }), _jsxs(Typography, { variant: "h3", children: ["Sensitive values are masked. For details, read", ' ', _jsxs("a", { href: DOCS_URL, target: "_blank", className: styles.externalLink, children: ["the docs", _jsx(ExternalIcon, { className: classNames(styles.externalIcon, styles.largeIcon) })] }), "."] })] }));
|
|
@@ -7,7 +7,5 @@ declare type Props = {
|
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
keyErrors?: (string | undefined)[];
|
|
9
9
|
};
|
|
10
|
-
export declare const MASKED_ENV_VALUE = "****";
|
|
11
|
-
export declare const isMaskedEnvValue: (value: string) => boolean;
|
|
12
10
|
export declare const EnvsEditor: ({ envs, onChange, suggestions, disabled, keyErrors, }: Props) => JSX.Element;
|
|
13
11
|
export {};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, IconButton, TextField, Typography } from '@material-ui/core';
|
|
3
3
|
import Box from '@mui/material/Box';
|
|
4
|
-
// MASKED_ENV_VALUE is what the engine returns instead of a stored env value:
|
|
5
|
-
// envs carry WAL-G and pgBackRest credentials, so GET /admin/config lists
|
|
6
|
-
// the keys only. Posting the mask back for a key keeps the stored value.
|
|
7
|
-
export const MASKED_ENV_VALUE = '****';
|
|
8
|
-
export const isMaskedEnvValue = (value) => value === MASKED_ENV_VALUE;
|
|
9
|
-
const MASKED_HINT = 'Stored on the server; kept unless you change it';
|
|
10
4
|
// EnvsEditor renders a rows-of-key/value editor with add/remove and a
|
|
11
5
|
// click-to-add suggestion list. Engine consumes envs as a free-form map
|
|
12
6
|
// (physical.go:76, CopyOptions.Envs map[string]string); the UI is a thin
|
|
@@ -26,6 +20,5 @@ export const EnvsEditor = ({ envs, onChange, suggestions = [], disabled, keyErro
|
|
|
26
20
|
onChange([...envs, { key, value: '' }]);
|
|
27
21
|
};
|
|
28
22
|
const usedKeys = new Set(envs.map((e) => e.key));
|
|
29
|
-
|
|
30
|
-
return (_jsxs(Box, { mt: 1, "data-testid": "envs-editor", children: [_jsx(Typography, { variant: "subtitle2", children: "Environment variables" }), hasMasked && (_jsx(Box, { mt: 0.5, children: _jsxs(Typography, { variant: "caption", color: "textSecondary", "data-testid": "envs-masked-hint", children: ["Values shown as ", MASKED_ENV_VALUE, " are stored on the server and are kept unless you change them. Renaming such a variable requires entering its value again."] }) })), 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, helperText: isMaskedEnvValue(env.value) ? MASKED_HINT : undefined, 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))) })] }))] }));
|
|
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))) })] }))] }));
|
|
31
24
|
};
|
|
@@ -42,11 +42,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
42
42
|
const classes = useStyles();
|
|
43
43
|
const stores = useStores();
|
|
44
44
|
const { config, isConfigurationLoading, updateConfig, getSeImages, fullConfig, testDbSource, configError, getFullConfig, getFullConfigError, getEngine, } = stores.main;
|
|
45
|
-
|
|
46
|
-
// directly. The store replaces the whole object on every fetch, so keying the clone on
|
|
47
|
-
// its identity keeps the copy — and everything that depends on it — stable between
|
|
48
|
-
// fetches instead of changing on every render.
|
|
49
|
-
const configData = useMemo(() => config && JSON.parse(JSON.stringify(config)), [config]);
|
|
45
|
+
const configData = config && JSON.parse(JSON.stringify(config));
|
|
50
46
|
const isConfigurationDisabled = disableConfigModification;
|
|
51
47
|
const [dleEdition, setDledition] = useState('');
|
|
52
48
|
const isCeEdition = dleEdition === 'community';
|
|
@@ -58,14 +54,11 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
58
54
|
// Prevents the Simple/Expert default from flipping mid-session when an
|
|
59
55
|
// Apply triggers a refetch and the recomputed default would differ.
|
|
60
56
|
const [initialMode, setInitialMode] = useState(null);
|
|
61
|
-
const defaultMode = configData
|
|
62
|
-
? getInitialConfigMode(configData.host, configData.retrievalMode)
|
|
63
|
-
: null;
|
|
64
57
|
useEffect(() => {
|
|
65
|
-
if (initialMode
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}, [
|
|
58
|
+
if (initialMode === null && configData) {
|
|
59
|
+
setInitialMode(getInitialConfigMode(configData.host, configData.retrievalMode));
|
|
60
|
+
}
|
|
61
|
+
}, [configData, initialMode]);
|
|
69
62
|
const configMode = (_a = userPickedMode !== null && userPickedMode !== void 0 ? userPickedMode : initialMode) !== null && _a !== void 0 ? _a : 'simple';
|
|
70
63
|
const setConfigMode = setUserPickedMode;
|
|
71
64
|
const portInitFromConfig = useRef(false);
|
|
@@ -258,7 +251,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
258
251
|
const currentValues = uniqueChipValue(String(formik.values[id]));
|
|
259
252
|
const splitValues = currentValues.split(' ');
|
|
260
253
|
const curDividers = String(formik.values[id]).match(/[,(\s)(\n)(\r)(\t)(\r\n)]/gm);
|
|
261
|
-
for (
|
|
254
|
+
for (let i in splitValues) {
|
|
262
255
|
if (curDividers && splitValues[i] !== uniqueValue) {
|
|
263
256
|
newValues =
|
|
264
257
|
newValues +
|
|
@@ -514,14 +507,14 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
514
507
|
isCeEdition,
|
|
515
508
|
]);
|
|
516
509
|
return (_jsxs("div", { className: styles.root, children: [_jsx(Snackbar, { onClick: () => {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
510
|
+
Boolean(dockerState.error)
|
|
511
|
+
? setDockerState({
|
|
512
|
+
...dockerState,
|
|
513
|
+
error: '',
|
|
514
|
+
})
|
|
515
|
+
: undefined;
|
|
523
516
|
}, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, open: (isConfigurationDisabled || Boolean(dockerState.error)) &&
|
|
524
|
-
!isModalOpen, message: dockerState.error
|
|
517
|
+
!isModalOpen, message: Boolean(dockerState.error)
|
|
525
518
|
? dockerState.error
|
|
526
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) => {
|
|
527
520
|
const projection = buildProjectionFromProposed(proposed, password);
|
|
@@ -593,7 +586,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
593
586
|
value: image,
|
|
594
587
|
children: image,
|
|
595
588
|
};
|
|
596
|
-
}) })] })), _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,
|
|
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'
|
|
597
590
|
? Object.entries(formik.values.tuningParams)
|
|
598
591
|
.map(([key, value]) => `${key}=${value}`)
|
|
599
592
|
.join('\n')
|
|
@@ -609,7 +602,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
609
602
|
: '', message: testConnectionState.fetchTuning.error ||
|
|
610
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: {
|
|
611
604
|
root: classes.checkboxRoot,
|
|
612
|
-
} }), 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,
|
|
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: {
|
|
613
606
|
display: 'flex',
|
|
614
607
|
alignItems: 'center',
|
|
615
608
|
}, children: [_jsxs(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
@@ -3,9 +3,9 @@ import styles from './styles.module.scss';
|
|
|
3
3
|
export const tooltipText = {
|
|
4
4
|
dockerTag: () => (_jsx("div", { children: "Docker image version \u2014 the latest ones are listed first. If you are unsure, pick the first one." })),
|
|
5
5
|
dockerImage: () => (_jsxs("div", { children: ["Major PostgreSQL version (e.g., \"9.6\", \"15\"). For logical provisioning mode, the version used by DBLab does not need to match the version on the source, although matching versions is recommended. ", _jsx("br", {}), "If you need a version that is not listed here, contact support."] })),
|
|
6
|
-
dockerImageType: () => (_jsxs("div", { children: ["Docker image used to run all database containers \u2014 clones, snapshot preparation containers, and sync containers. Although such images are based on traditional Docker images for PostgreSQL, DBLab expects slightly different behavior: for example, PostgreSQL is not the first process used to start the container, so PostgreSQL restarts do not trigger a container state change. For details, see", ' ', _jsx("a", { target: '_blank', href: 'https://postgres.ai/docs/database-lab/supported-databases', className: styles.externalLink,
|
|
6
|
+
dockerImageType: () => (_jsxs("div", { children: ["Docker image used to run all database containers \u2014 clones, snapshot preparation containers, and sync containers. Although such images are based on traditional Docker images for PostgreSQL, DBLab expects slightly different behavior: for example, PostgreSQL is not the first process used to start the container, so PostgreSQL restarts do not trigger a container state change. For details, see", ' ', _jsx("a", { target: '_blank', href: 'https://postgres.ai/docs/database-lab/supported-databases', className: styles.externalLink, children: "the docs" }), "."] })),
|
|
7
7
|
sharedBuffers: () => (_jsxs("div", { children: ["Defines the default buffer pool size for each PostgreSQL instance managed by DBLab. Note that this amount of RAM is immediately allocated at PostgreSQL startup time. For example, if the machine running DBLab has 32 GiB of RAM and the value used here is '1GB', then the theoretical limit of clones is 32. Practically, this limit is even lower because some memory is consumed by other processes. If you need more clones, reduce the value of", ' ', _jsx("span", { className: styles.firaCodeFont, children: "configs.shared_buffers" }), "."] })),
|
|
8
|
-
sharedPreloadLibraries: () => (_jsxs("div", { children: ["Specifies one or more shared libraries (comma-separated list) to be preloaded at PostgreSQL server start (", _jsx("a", { target: '_blank', href: 'https://postgresqlco.nf/doc/en/param/shared_preload_libraries/', className: styles.externalLink,
|
|
8
|
+
sharedPreloadLibraries: () => (_jsxs("div", { children: ["Specifies one or more shared libraries (comma-separated list) to be preloaded at PostgreSQL server start (", _jsx("a", { target: '_blank', href: 'https://postgresqlco.nf/doc/en/param/shared_preload_libraries/', className: styles.externalLink, children: "details" }), "). If some libraries or extensions are missing, PostgreSQL fails to start, so make sure that ", _jsx("span", { className: styles.firaCodeFont, children: "dockerImage" }), ' ', "used above contains all required extensions."] })),
|
|
9
9
|
host: () => (_jsx("div", { children: "Hostname or IP of the database that will be used as the source for data retrieval (full data refresh)." })),
|
|
10
10
|
port: () => (_jsx("div", { children: "Port of the database that will be used as the source for data retrieval (full data refresh)." })),
|
|
11
11
|
username: () => (_jsx("div", { children: "Username used to connect to the database that will be used as the source for data retrieval (full data refresh)." })),
|
|
@@ -17,6 +17,6 @@ export const tooltipText = {
|
|
|
17
17
|
restoreParallelJobs: () => (_jsx("div", { children: "Number of parallel workers used to restore databases from dump to PostgreSQL managed by DBLab. For initial data retrieval (the first data refresh), it is recommended to match the number of available vCPUs on the machine running DBLab. This yields faster restore times but can increase CPU and disk I/O usage on that machine (up to temporary resource saturation). For subsequent refreshes, if DBLab is in continuous use, it is recommended to reduce this value by 50% to reserve capacity for normal DBLab operations (such as working with clones)." })),
|
|
18
18
|
pgRestoreCustomOptions: () => (_jsx("div", { children: "pg_restore options to be used to restore from a database dump, for example: '--exclude-schema=repack --exclude-schema=\"camelStyleSchemaName\"'. Note that due to security reasons, the current implementation supports only letters, numbers, hyphen, underscore, equal sign, and double quotes." })),
|
|
19
19
|
restoreConfigs: () => (_jsxs("div", { children: ["PostgreSQL configuration parameters applied during logical restore (one", ' ', _jsx("span", { className: styles.firaCodeFont, children: "parameter=value" }), " per line). These settings are written to", ' ', _jsx("span", { className: styles.firaCodeFont, children: "postgresql.conf" }), " before restore starts and do not affect clones. Useful for tuning restore performance, for example:", _jsx("br", {}), _jsx("span", { className: styles.firaCodeFont, children: "maintenance_work_mem=8GB" }), _jsx("br", {}), _jsx("span", { className: styles.firaCodeFont, children: "max_parallel_maintenance_workers=7" }), _jsx("br", {}), _jsx("span", { className: styles.firaCodeFont, children: "shared_preload_libraries=" }), _jsx("br", {}), _jsx("span", { className: styles.firaCodeFont, children: "fsync=off" })] })),
|
|
20
|
-
timetable: () => (_jsxs("div", { children: ["Schedule for full data refreshes, in", ' ', _jsx("a", { target: '_blank', href: 'https://en.wikipedia.org/wiki/Cron#Overview', className: styles.externalLink,
|
|
21
|
-
tuningParams: () => (_jsxs("div", { children: ["Query tuning parameters. These are essential to ensure that cloned PostgreSQL instances generate the same plans as the source (specifically, they are crucial for query performance troubleshooting and optimization, including working with EXPLAIN plans). For details, see the", ' ', _jsx("a", { target: '_blank', href: 'https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones', className: styles.externalLink,
|
|
20
|
+
timetable: () => (_jsxs("div", { children: ["Schedule for full data refreshes, in", ' ', _jsx("a", { target: '_blank', href: 'https://en.wikipedia.org/wiki/Cron#Overview', className: styles.externalLink, children: "crontab format" }), "."] })),
|
|
21
|
+
tuningParams: () => (_jsxs("div", { children: ["Query tuning parameters. These are essential to ensure that cloned PostgreSQL instances generate the same plans as the source (specifically, they are crucial for query performance troubleshooting and optimization, including working with EXPLAIN plans). For details, see the", ' ', _jsx("a", { target: '_blank', href: 'https://postgres.ai/docs/how-to-guides/administration/postgresql-configuration#postgresql-configuration-in-clones', className: styles.externalLink, children: "docs" }), "."] })),
|
|
22
22
|
};
|
|
@@ -154,7 +154,7 @@ export const useForm = (onSubmit) => {
|
|
|
154
154
|
const markPortDirty = () => setPortDirty(true);
|
|
155
155
|
const omitPortOnSubmit = originalPortWasUnset && !portDirty;
|
|
156
156
|
const formatDatabaseArray = (database) => {
|
|
157
|
-
|
|
157
|
+
let databases = [];
|
|
158
158
|
const splitDatabaseArray = database.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
|
|
159
159
|
for (let i = 0; i < splitDatabaseArray.length; i++) {
|
|
160
160
|
if (splitDatabaseArray[i] !== '') {
|
|
@@ -9,7 +9,7 @@ interface DockerImage {
|
|
|
9
9
|
}
|
|
10
10
|
export declare const uniqueChipValue: (values: string) => string;
|
|
11
11
|
export declare const postUniqueDatabases: (values: string) => {
|
|
12
|
-
[k: string]: string |
|
|
12
|
+
[k: string]: string | Object;
|
|
13
13
|
} | null;
|
|
14
14
|
export declare const genericDockerImages: DockerImage[];
|
|
15
15
|
export declare const isSeDockerImage: (dockerImage: string | undefined) => boolean;
|
|
@@ -3,8 +3,8 @@ import { dockerImagesConfig, genericImagePrefix } from '../dockerCatalog';
|
|
|
3
3
|
const seContainerRegistry = 'se-images';
|
|
4
4
|
export const uniqueChipValue = (values) => {
|
|
5
5
|
const splitChipArray = values.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
|
|
6
|
-
|
|
7
|
-
for (
|
|
6
|
+
let databaseArray = [];
|
|
7
|
+
for (let i in splitChipArray) {
|
|
8
8
|
if (splitChipArray[i] !== '' &&
|
|
9
9
|
databaseArray.indexOf(splitChipArray[i]) === -1) {
|
|
10
10
|
databaseArray.push(splitChipArray[i]);
|
|
@@ -24,18 +24,18 @@ export const postUniqueDatabases = (values) => {
|
|
|
24
24
|
const createDockerImages = (dockerImagesConfig) => {
|
|
25
25
|
const dockerImages = [];
|
|
26
26
|
for (const pg_major_version in dockerImagesConfig) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
27
|
+
if (dockerImagesConfig.hasOwnProperty(pg_major_version)) {
|
|
28
|
+
const customTags = dockerImagesConfig[pg_major_version];
|
|
29
|
+
customTags.forEach((tag) => {
|
|
30
|
+
const image = {
|
|
31
|
+
package_group: 'postgresai',
|
|
32
|
+
pg_major_version,
|
|
33
|
+
tag: `${pg_major_version}-${tag}`,
|
|
34
|
+
location: `${genericImagePrefix}:${pg_major_version}-${tag}`,
|
|
35
|
+
};
|
|
36
|
+
dockerImages.push(image);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
39
|
}
|
|
40
40
|
return dockerImages;
|
|
41
41
|
};
|
|
@@ -74,7 +74,7 @@ export const getImageMajorVersion = (pgImage) => {
|
|
|
74
74
|
? pgServerVersion.split('.')[0]
|
|
75
75
|
: pgServerVersion;
|
|
76
76
|
}
|
|
77
|
-
catch {
|
|
77
|
+
catch (error) {
|
|
78
78
|
// Return undefined for malformed image strings
|
|
79
79
|
return undefined;
|
|
80
80
|
}
|
|
@@ -114,7 +114,7 @@ export const createFallbackDockerImage = (dockerPath, dockerTag) => {
|
|
|
114
114
|
};
|
|
115
115
|
// Creates enhanced list of Docker images, including image from configuration
|
|
116
116
|
export const createEnhancedDockerImages = (configDockerPath, configDockerTag) => {
|
|
117
|
-
|
|
117
|
+
let enhancedImages = [...genericDockerImages];
|
|
118
118
|
// If there's an image in config, check if we need to add it
|
|
119
119
|
if (configDockerPath && configDockerTag) {
|
|
120
120
|
const existingImage = genericDockerImages.find((image) => image.location === configDockerPath || image.tag === configDockerTag);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
declare type Props = {
|
|
3
3
|
value: number;
|
|
4
|
-
handleChange: (event: React.ChangeEvent<
|
|
4
|
+
handleChange: (event: React.ChangeEvent<{}>, newValue: number) => void;
|
|
5
5
|
hasLogs: boolean;
|
|
6
6
|
isPlatform?: boolean;
|
|
7
7
|
hideInstanceTabs?: boolean;
|
|
8
8
|
};
|
|
9
|
-
export declare const PlatformTabs: ({ value, handleChange, hideInstanceTabs, }: Props) => JSX.Element;
|
|
9
|
+
export declare const PlatformTabs: ({ value, handleChange, hasLogs, hideInstanceTabs, }: Props) => JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -4,7 +4,7 @@ import { Tab as TabComponent, Tabs as TabsComponent } from '@material-ui/core';
|
|
|
4
4
|
import { TABS_INDEX } from '.';
|
|
5
5
|
import { useTabsStyles } from './styles';
|
|
6
6
|
import { PostgresSQLIcon } from '@postgres.ai/shared/icons/PostgresSQL';
|
|
7
|
-
export const PlatformTabs = ({ value, handleChange, hideInstanceTabs, }) => {
|
|
7
|
+
export const PlatformTabs = ({ value, handleChange, hasLogs, hideInstanceTabs, }) => {
|
|
8
8
|
const classes = useTabsStyles();
|
|
9
9
|
const { org, instanceId } = useParams();
|
|
10
10
|
const tabs = [
|
|
@@ -9,7 +9,7 @@ export declare const TABS_INDEX: {
|
|
|
9
9
|
};
|
|
10
10
|
export interface TabsProps {
|
|
11
11
|
value: number;
|
|
12
|
-
handleChange: (event: React.ChangeEvent<
|
|
12
|
+
handleChange: (event: React.ChangeEvent<{}>, newValue: number) => void;
|
|
13
13
|
hasLogs: boolean;
|
|
14
14
|
isPlatform?: boolean;
|
|
15
15
|
hideInstanceTabs?: boolean;
|
package/pages/Instance/index.js
CHANGED
|
@@ -50,7 +50,7 @@ const useStyles = makeStyles((theme) => ({
|
|
|
50
50
|
},
|
|
51
51
|
}), { index: 1 });
|
|
52
52
|
export const Instance = observer((props) => {
|
|
53
|
-
var _a, _b, _c, _d
|
|
53
|
+
var _a, _b, _c, _d;
|
|
54
54
|
const classes = useStyles();
|
|
55
55
|
const { instanceId, api, isPlatform } = props;
|
|
56
56
|
const [activeTab, setActiveTab] = React.useState((props === null || props === void 0 ? void 0 : props.renderCurrentTab) || TABS_INDEX.OVERVIEW);
|
|
@@ -84,7 +84,7 @@ export const Instance = observer((props) => {
|
|
|
84
84
|
}
|
|
85
85
|
}, [instance, hasBeenRedirected]);
|
|
86
86
|
return (_jsx(HostProvider, { value: props, children: _jsxs(StoresProvider, { value: stores, children: [props.elements.breadcrumbs, _jsx(SectionTitle, { text: props.title, level: 1, tag: "h1", className: classes.title, rightContent: _jsx(Button, { onClick: () => load(props.instanceId, isPlatform), isDisabled: !instance && !instanceError, className: classes.reloadButton, children: "Reload info" }), children: isInstanceIntegrated && (_jsx(InstanceTabs, { instanceId: props.instanceId, tab: activeTab, onTabChange: (tabID) => setActiveTab(tabID), isPlatform: isPlatform, hasLogs: api.initWS !== undefined, hideInstanceTabs: props.hideBranchingFeatures })) }), instanceError && (_jsx(ErrorStub, { ...instanceError, className: classes.errorStub })), isInstanceIntegrated ? (_jsxs(_Fragment, { children: [_jsxs(TabPanel, { value: activeTab, index: TABS_INDEX.OVERVIEW, children: [!instanceError && (_jsx("div", { className: classes.content, children: instance && ((_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.retrieving) === null || _b === void 0 ? void 0 : _b.status) ? (_jsxs(_Fragment, { children: [_jsx(Clones, {}), _jsx(Info, { hideBranchingFeatures: props.hideBranchingFeatures })] })) : (_jsx(StubSpinner, {})) })), _jsx(ClonesModal, {}), _jsx(SnapshotsModal, {})] }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CLONES, children: activeTab === TABS_INDEX.CLONES && (_jsx("div", { className: classes.content, children: !instanceError &&
|
|
87
|
-
(instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (
|
|
87
|
+
(instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org }) })) : (!instanceError && (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx("div", { className: classes.content, children: _jsx(StubSpinner, {}) }) })))] }) }));
|
|
88
88
|
});
|
|
89
89
|
function TabPanel(props) {
|
|
90
90
|
const { children, value, index, ...other } = props;
|
|
@@ -164,7 +164,7 @@ export class MainStore {
|
|
|
164
164
|
return response;
|
|
165
165
|
};
|
|
166
166
|
this.getFullConfig = async (instanceId) => {
|
|
167
|
-
var _a, _b, _c, _d, _e, _f
|
|
167
|
+
var _a, _b, _c, _d, _e, _f;
|
|
168
168
|
if (!this.api.getFullConfig)
|
|
169
169
|
return;
|
|
170
170
|
const { response, error } = await this.api.getFullConfig(instanceId);
|
|
@@ -176,10 +176,10 @@ export class MainStore {
|
|
|
176
176
|
this.platformUrl = rawPlatformUrl === null || rawPlatformUrl === void 0 ? void 0 : rawPlatformUrl.replace(/['"]+/g, '').trim().split(/\s+/)[0];
|
|
177
177
|
this.uiVersion = (_f = (_e = (_d = splitYML[0]) === null || _d === void 0 ? void 0 : _d.split('dockerImage: "postgresai/ce-ui:')[2]) === null || _e === void 0 ? void 0 : _e.split('\n')[0]) === null || _f === void 0 ? void 0 : _f.replace(/['"]+/g, '');
|
|
178
178
|
}
|
|
179
|
-
if (error)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
if (error)
|
|
180
|
+
this.getFullConfigError = await error
|
|
181
|
+
.json()
|
|
182
|
+
.then((err) => err.message);
|
|
183
183
|
return response;
|
|
184
184
|
};
|
|
185
185
|
this.getSeImages = async (values) => {
|
|
@@ -5,41 +5,33 @@ export const useWsScroll = (isLoading, simpleInstall) => {
|
|
|
5
5
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
6
6
|
useEffect(() => {
|
|
7
7
|
var _a;
|
|
8
|
-
|
|
9
|
-
wsSnackbar(isAtBottom, isNewData);
|
|
10
|
-
}
|
|
8
|
+
!isLoading && wsSnackbar(isAtBottom, isNewData);
|
|
11
9
|
const contentElement = document.getElementById('content-container');
|
|
12
10
|
const targetNode = simpleInstall
|
|
13
11
|
? (_a = document.getElementById('logs-container')) === null || _a === void 0 ? void 0 : _a.parentElement
|
|
14
12
|
: document.getElementById('logs-container');
|
|
15
13
|
const clientAtBottom = (element) => element.scrollHeight - element.scrollTop - 50 < element.clientHeight;
|
|
16
14
|
const handleScroll = (e) => {
|
|
17
|
-
if (
|
|
15
|
+
if (clientAtBottom(e.target)) {
|
|
16
|
+
setIsAtBottom(true);
|
|
17
|
+
setIsNewData(false);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
18
20
|
setIsAtBottom(false);
|
|
19
|
-
return;
|
|
20
21
|
}
|
|
21
|
-
setIsAtBottom(true);
|
|
22
|
-
setIsNewData(false);
|
|
23
22
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return;
|
|
30
|
-
if (isAtBottom) {
|
|
31
|
-
targetNode === null || targetNode === void 0 ? void 0 : targetNode.scrollIntoView(false);
|
|
23
|
+
const handleInsert = (e) => {
|
|
24
|
+
var _a;
|
|
25
|
+
if (((_a = e.srcElement) === null || _a === void 0 ? void 0 : _a.tagName) !== 'DIV') {
|
|
26
|
+
isAtBottom && (targetNode === null || targetNode === void 0 ? void 0 : targetNode.scrollIntoView(false));
|
|
27
|
+
setIsNewData(true);
|
|
32
28
|
}
|
|
33
|
-
setIsNewData(true);
|
|
34
29
|
};
|
|
35
|
-
const observer = new MutationObserver(handleInsert);
|
|
36
30
|
contentElement === null || contentElement === void 0 ? void 0 : contentElement.addEventListener('scroll', handleScroll, false);
|
|
37
|
-
|
|
38
|
-
observer.observe(contentElement, { childList: true, subtree: true });
|
|
39
|
-
}
|
|
31
|
+
contentElement === null || contentElement === void 0 ? void 0 : contentElement.addEventListener('DOMNodeInserted', handleInsert, false);
|
|
40
32
|
return () => {
|
|
41
33
|
contentElement === null || contentElement === void 0 ? void 0 : contentElement.removeEventListener('scroll', handleScroll, false);
|
|
42
|
-
|
|
34
|
+
contentElement === null || contentElement === void 0 ? void 0 : contentElement.removeEventListener('DOMNodeInserted', handleInsert, false);
|
|
43
35
|
};
|
|
44
|
-
}, [isAtBottom, isNewData, isLoading
|
|
36
|
+
}, [isAtBottom, isNewData, isLoading]);
|
|
45
37
|
};
|