@postgres.ai/shared 4.2.0-pr-1201 → 4.2.0-pr-1203.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 +1 -0
- package/components/Button2/index.js +1 -0
- package/components/ErrorBoundary/index.d.ts +15 -0
- package/components/ErrorBoundary/index.js +24 -0
- package/components/Status/index.js +1 -0
- 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 +1 -3
- package/icons/Renewable/index.js +1 -0
- package/icons/Shield/index.js +1 -0
- package/icons/Warning/index.js +1 -3
- package/package.json +1 -1
- package/pages/Branches/index.js +4 -2
- package/pages/Clone/Status/index.js +1 -0
- 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 +2 -0
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +8 -1
- package/pages/Instance/Configuration/index.js +22 -15
- 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 +22 -14
- package/pages/Logs/index.js +14 -18
- package/pages/Logs/utils/index.d.ts +2 -0
- package/pages/Logs/utils/index.js +20 -1
- package/pages/Logs/wsLogs.d.ts +1 -0
- package/pages/Logs/wsLogs.js +32 -5
- package/pages/Snapshots/Snapshot/stores/Main.js +2 -1
- package/styles/icons.js +1 -1
- package/styles/styles.js +3 -0
- package/styles/theme.js +1 -2
- package/types/api/endpoints/getFullConfig.d.ts +1 -1
- package/types/api/endpoints/testDbSource.js +2 -2
- package/types/api/entities/clone.d.ts +9 -0
- package/types/api/entities/clone.js +1 -0
- package/types/api/entities/config.d.ts +1 -1
- package/types/api/entities/instance.d.ts +5 -0
- package/types/api/entities/instanceRetrieval.d.ts +2 -2
- package/types/api/entities/instanceState.d.ts +5 -0
- package/utils/api.js +2 -2
|
@@ -38,3 +38,4 @@ 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,3 +8,4 @@ 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';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Component, ErrorInfo, ReactNode } from 'react';
|
|
2
|
+
declare type Props = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
fallback?: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
declare type State = {
|
|
7
|
+
error: Error | null;
|
|
8
|
+
};
|
|
9
|
+
export declare class ErrorBoundary extends Component<Props, State> {
|
|
10
|
+
state: State;
|
|
11
|
+
static getDerivedStateFromError(error: Error): State;
|
|
12
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
13
|
+
render(): {} | null | undefined;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Component } from 'react';
|
|
3
|
+
import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub';
|
|
4
|
+
// React unmounts the entire tree when a render throws and no boundary catches it, leaving a
|
|
5
|
+
// blank page with nothing pointing at the cause.
|
|
6
|
+
export class ErrorBoundary extends Component {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.state = { error: null };
|
|
10
|
+
}
|
|
11
|
+
static getDerivedStateFromError(error) {
|
|
12
|
+
return { error };
|
|
13
|
+
}
|
|
14
|
+
componentDidCatch(error, info) {
|
|
15
|
+
console.error('Unhandled UI error:', error, info.componentStack);
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
if (!this.state.error)
|
|
19
|
+
return this.props.children;
|
|
20
|
+
if (this.props.fallback)
|
|
21
|
+
return this.props.fallback;
|
|
22
|
+
return (_jsx(ErrorStub, { title: "Something went wrong", message: this.state.error.message }));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -21,3 +21,4 @@ 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,
|
|
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, children: props.children }));
|
|
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
|
+
const 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
|
+
const 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,6 +1,4 @@
|
|
|
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", {
|
|
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" }) }));
|
|
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", { 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" }) }));
|
|
6
4
|
};
|
package/icons/Renewable/index.js
CHANGED
|
@@ -10,3 +10,4 @@ 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,3 +10,4 @@ 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,6 +1,4 @@
|
|
|
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", {
|
|
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" }) }));
|
|
3
|
+
return (_jsx("svg", { className: props.className, viewBox: "0 0 10 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { 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" }) }));
|
|
6
4
|
};
|
package/package.json
CHANGED
package/pages/Branches/index.js
CHANGED
|
@@ -48,7 +48,8 @@ export const Branches = observer(({ instanceId }) => {
|
|
|
48
48
|
const loadBranches = () => {
|
|
49
49
|
getBranches(instanceId)
|
|
50
50
|
.then((response) => {
|
|
51
|
-
|
|
51
|
+
if (response)
|
|
52
|
+
setBranches(response);
|
|
52
53
|
})
|
|
53
54
|
.finally(() => setIsLoading(false));
|
|
54
55
|
};
|
|
@@ -56,7 +57,8 @@ export const Branches = observer(({ instanceId }) => {
|
|
|
56
57
|
loadBranches();
|
|
57
58
|
const intervalId = setInterval(() => {
|
|
58
59
|
getBranches(instanceId, true).then((response) => {
|
|
59
|
-
|
|
60
|
+
if (response)
|
|
61
|
+
setBranches(response);
|
|
60
62
|
});
|
|
61
63
|
}, listRefreshIntervalMs);
|
|
62
64
|
return () => clearInterval(intervalId);
|
|
@@ -39,3 +39,4 @@ 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-expect-error TextField forwards readOnly to the input but does not declare it
|
|
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-expect-error TextField forwards readOnly to the input but does not declare it
|
|
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-expect-error TextField forwards readOnly to the input but does not declare it
|
|
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
|
+
const 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", 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) })] }), "."] })] }));
|
|
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", rel: "noreferrer", 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", rel: "noreferrer", className: styles.externalLink, children: ["the docs", _jsx(ExternalIcon, { className: classNames(styles.externalIcon, styles.largeIcon) })] }), "."] })] }));
|
|
@@ -7,5 +7,7 @@ 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;
|
|
10
12
|
export declare const EnvsEditor: ({ envs, onChange, suggestions, disabled, keyErrors, }: Props) => JSX.Element;
|
|
11
13
|
export {};
|
|
@@ -1,6 +1,12 @@
|
|
|
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';
|
|
4
10
|
// EnvsEditor renders a rows-of-key/value editor with add/remove and a
|
|
5
11
|
// click-to-add suggestion list. Engine consumes envs as a free-form map
|
|
6
12
|
// (physical.go:76, CopyOptions.Envs map[string]string); the UI is a thin
|
|
@@ -20,5 +26,6 @@ export const EnvsEditor = ({ envs, onChange, suggestions = [], disabled, keyErro
|
|
|
20
26
|
onChange([...envs, { key, value: '' }]);
|
|
21
27
|
};
|
|
22
28
|
const usedKeys = new Set(envs.map((e) => e.key));
|
|
23
|
-
|
|
29
|
+
const hasMasked = envs.some((e) => isMaskedEnvValue(e.value));
|
|
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))) })] }))] }));
|
|
24
31
|
};
|
|
@@ -42,7 +42,11 @@ 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
|
-
|
|
45
|
+
// The form mutates its working copy, so the store's config is cloned rather than read
|
|
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]);
|
|
46
50
|
const isConfigurationDisabled = disableConfigModification;
|
|
47
51
|
const [dleEdition, setDledition] = useState('');
|
|
48
52
|
const isCeEdition = dleEdition === 'community';
|
|
@@ -54,11 +58,14 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
54
58
|
// Prevents the Simple/Expert default from flipping mid-session when an
|
|
55
59
|
// Apply triggers a refetch and the recomputed default would differ.
|
|
56
60
|
const [initialMode, setInitialMode] = useState(null);
|
|
61
|
+
const defaultMode = configData
|
|
62
|
+
? getInitialConfigMode(configData.host, configData.retrievalMode)
|
|
63
|
+
: null;
|
|
57
64
|
useEffect(() => {
|
|
58
|
-
if (initialMode
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}, [
|
|
65
|
+
if (initialMode !== null || defaultMode === null)
|
|
66
|
+
return;
|
|
67
|
+
setInitialMode(defaultMode);
|
|
68
|
+
}, [defaultMode, initialMode]);
|
|
62
69
|
const configMode = (_a = userPickedMode !== null && userPickedMode !== void 0 ? userPickedMode : initialMode) !== null && _a !== void 0 ? _a : 'simple';
|
|
63
70
|
const setConfigMode = setUserPickedMode;
|
|
64
71
|
const portInitFromConfig = useRef(false);
|
|
@@ -251,7 +258,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
251
258
|
const currentValues = uniqueChipValue(String(formik.values[id]));
|
|
252
259
|
const splitValues = currentValues.split(' ');
|
|
253
260
|
const curDividers = String(formik.values[id]).match(/[,(\s)(\n)(\r)(\t)(\r\n)]/gm);
|
|
254
|
-
for (
|
|
261
|
+
for (const i in splitValues) {
|
|
255
262
|
if (curDividers && splitValues[i] !== uniqueValue) {
|
|
256
263
|
newValues =
|
|
257
264
|
newValues +
|
|
@@ -507,14 +514,14 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
507
514
|
isCeEdition,
|
|
508
515
|
]);
|
|
509
516
|
return (_jsxs("div", { className: styles.root, children: [_jsx(Snackbar, { onClick: () => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
517
|
+
if (!dockerState.error)
|
|
518
|
+
return;
|
|
519
|
+
setDockerState({
|
|
520
|
+
...dockerState,
|
|
521
|
+
error: '',
|
|
522
|
+
});
|
|
516
523
|
}, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, open: (isConfigurationDisabled || Boolean(dockerState.error)) &&
|
|
517
|
-
!isModalOpen, message:
|
|
524
|
+
!isModalOpen, message: dockerState.error
|
|
518
525
|
? dockerState.error
|
|
519
526
|
: 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
527
|
const projection = buildProjectionFromProposed(proposed, password);
|
|
@@ -586,7 +593,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
586
593
|
value: image,
|
|
587
594
|
children: image,
|
|
588
595
|
};
|
|
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'
|
|
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, rel: "noreferrer", 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
597
|
? Object.entries(formik.values.tuningParams)
|
|
591
598
|
.map(([key, value]) => `${key}=${value}`)
|
|
592
599
|
.join('\n')
|
|
@@ -602,7 +609,7 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, di
|
|
|
602
609
|
: '', message: testConnectionState.fetchTuning.error ||
|
|
603
610
|
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
611
|
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: {
|
|
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, rel: "noreferrer", 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
613
|
display: 'flex',
|
|
607
614
|
alignItems: 'center',
|
|
608
615
|
}, 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, children: "the docs" }), "."] })),
|
|
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, rel: "noreferrer", 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, 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."] })),
|
|
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, rel: "noreferrer", 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, 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" }), "."] })),
|
|
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, rel: "noreferrer", 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, rel: "noreferrer", 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
|
+
const 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
|
+
const databaseArray = [];
|
|
7
|
+
for (const 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
|
+
const hasVersion = Object.prototype.hasOwnProperty.call(dockerImagesConfig, pg_major_version);
|
|
28
|
+
if (!hasVersion)
|
|
29
|
+
continue;
|
|
30
|
+
dockerImagesConfig[pg_major_version].forEach((tag) => {
|
|
31
|
+
const image = {
|
|
32
|
+
package_group: 'postgresai',
|
|
33
|
+
pg_major_version,
|
|
34
|
+
tag: `${pg_major_version}-${tag}`,
|
|
35
|
+
location: `${genericImagePrefix}:${pg_major_version}-${tag}`,
|
|
36
|
+
};
|
|
37
|
+
dockerImages.push(image);
|
|
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 {
|
|
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
|
+
const 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<object>, newValue: number) => void;
|
|
5
5
|
hasLogs: boolean;
|
|
6
6
|
isPlatform?: boolean;
|
|
7
7
|
hideInstanceTabs?: boolean;
|
|
8
8
|
};
|
|
9
|
-
export declare const PlatformTabs: ({ value, handleChange,
|
|
9
|
+
export declare const PlatformTabs: ({ value, handleChange, 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,
|
|
7
|
+
export const PlatformTabs = ({ value, handleChange, 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<object>, 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, _e;
|
|
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: (_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, {}) }) })))] }) }));
|
|
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: (_e = (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org) !== null && _e !== void 0 ? _e : '' }) })) : (!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, _g;
|
|
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
|
+
const failure = (await error.json());
|
|
181
|
+
this.getFullConfigError = (_g = failure.message) !== null && _g !== void 0 ? _g : null;
|
|
182
|
+
}
|
|
183
183
|
return response;
|
|
184
184
|
};
|
|
185
185
|
this.getSeImages = async (values) => {
|