@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.
Files changed (51) hide show
  1. package/components/Button/index.js +1 -0
  2. package/components/Button2/index.js +1 -0
  3. package/components/ErrorBoundary/index.d.ts +15 -0
  4. package/components/ErrorBoundary/index.js +24 -0
  5. package/components/Status/index.js +1 -0
  6. package/components/TextField/index.js +1 -1
  7. package/config/index.js +1 -1
  8. package/helpers/getEntropy.js +2 -2
  9. package/helpers/request.js +1 -1
  10. package/icons/ArrowDropDown/index.js +1 -3
  11. package/icons/Renewable/index.js +1 -0
  12. package/icons/Shield/index.js +1 -0
  13. package/icons/Warning/index.js +1 -3
  14. package/package.json +1 -1
  15. package/pages/Branches/index.js +4 -2
  16. package/pages/Clone/Status/index.js +1 -0
  17. package/pages/Clone/index.js +3 -3
  18. package/pages/CreateClone/index.js +1 -1
  19. package/pages/CreateClone/utils/index.js +1 -1
  20. package/pages/Instance/Configuration/Header/index.js +2 -2
  21. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +2 -0
  22. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +8 -1
  23. package/pages/Instance/Configuration/index.js +22 -15
  24. package/pages/Instance/Configuration/tooltipText.js +4 -4
  25. package/pages/Instance/Configuration/useForm.js +1 -1
  26. package/pages/Instance/Configuration/utils/index.d.ts +1 -1
  27. package/pages/Instance/Configuration/utils/index.js +16 -16
  28. package/pages/Instance/Tabs/PlatformTabs.d.ts +2 -2
  29. package/pages/Instance/Tabs/PlatformTabs.js +1 -1
  30. package/pages/Instance/Tabs/index.d.ts +1 -1
  31. package/pages/Instance/index.js +2 -2
  32. package/pages/Instance/stores/Main.js +5 -5
  33. package/pages/Logs/hooks/useWsScroll.js +22 -14
  34. package/pages/Logs/index.js +14 -18
  35. package/pages/Logs/utils/index.d.ts +2 -0
  36. package/pages/Logs/utils/index.js +20 -1
  37. package/pages/Logs/wsLogs.d.ts +1 -0
  38. package/pages/Logs/wsLogs.js +32 -5
  39. package/pages/Snapshots/Snapshot/stores/Main.js +2 -1
  40. package/styles/icons.js +1 -1
  41. package/styles/styles.js +3 -0
  42. package/styles/theme.js +1 -2
  43. package/types/api/endpoints/getFullConfig.d.ts +1 -1
  44. package/types/api/endpoints/testDbSource.js +2 -2
  45. package/types/api/entities/clone.d.ts +9 -0
  46. package/types/api/entities/clone.js +1 -0
  47. package/types/api/entities/config.d.ts +1 -1
  48. package/types/api/entities/instance.d.ts +5 -0
  49. package/types/api/entities/instanceRetrieval.d.ts +2 -2
  50. package/types/api/entities/instanceState.d.ts +5 -0
  51. 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, 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 }));
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
@@ -14,7 +14,7 @@ const loadDateFnsLocale = async () => {
14
14
  config.dateFnsLocale = locale.default;
15
15
  return;
16
16
  }
17
- catch (e) {
17
+ catch {
18
18
  // Unavailable locale.
19
19
  }
20
20
  };
@@ -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
- let uniqueChars = [];
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
- let runes = Array.from(s);
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++) {
@@ -26,7 +26,7 @@ export const request = async (path, options) => {
26
26
  },
27
27
  });
28
28
  }
29
- catch (e) {
29
+ catch {
30
30
  return new Response(null, {
31
31
  status: 500,
32
32
  statusText: `Unknown error`,
@@ -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
  };
@@ -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';
@@ -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';
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postgres.ai/shared",
3
- "version": "4.2.0-pr-1201",
3
+ "version": "4.2.0-pr-1203.1",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "peerDependencies": {
@@ -48,7 +48,8 @@ export const Branches = observer(({ instanceId }) => {
48
48
  const loadBranches = () => {
49
49
  getBranches(instanceId)
50
50
  .then((response) => {
51
- response && setBranches(response);
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
- response && setBranches(response);
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';
@@ -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-ignore
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-ignore
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-ignore
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
- let selectedSnapshot = allSnapshots.find(s => s.id === initialSnapshotId) || allSnapshots[0];
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
- 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))) })] }))] }));
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
- const configData = config && JSON.parse(JSON.stringify(config));
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 === null && configData) {
59
- setInitialMode(getInitialConfigMode(configData.host, configData.retrievalMode));
60
- }
61
- }, [configData, initialMode]);
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 (let i in splitValues) {
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
- Boolean(dockerState.error)
511
- ? setDockerState({
512
- ...dockerState,
513
- error: '',
514
- })
515
- : undefined;
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: Boolean(dockerState.error)
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
- let databases = [];
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 | Object;
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
- let databaseArray = [];
7
- for (let i in splitChipArray) {
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
- 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
- }
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 (error) {
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
- let enhancedImages = [...genericDockerImages];
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<{}>, newValue: number) => void;
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, hasLogs, hideInstanceTabs, }: Props) => JSX.Element;
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, hasLogs, hideInstanceTabs, }) => {
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<{}>, newValue: number) => void;
12
+ handleChange: (event: React.ChangeEvent<object>, newValue: number) => void;
13
13
  hasLogs: boolean;
14
14
  isPlatform?: boolean;
15
15
  hideInstanceTabs?: boolean;
@@ -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
- this.getFullConfigError = await error
181
- .json()
182
- .then((err) => err.message);
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) => {