@postgres.ai/shared 4.0.2-pr-1061.1 → 4.0.2-pr-1118

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 (37) hide show
  1. package/components/Select/index.d.ts +1 -0
  2. package/components/TextField/index.d.ts +2 -1
  3. package/components/TextField/index.js +1 -1
  4. package/helpers/getEntropy.d.ts +8 -0
  5. package/helpers/getEntropy.js +39 -0
  6. package/icons/PostgresSQL/index.js +1 -39
  7. package/package.json +2 -2
  8. package/pages/Clone/index.js +49 -6
  9. package/pages/Clone/stores/Main.d.ts +1 -1
  10. package/pages/Clone/stores/Main.js +8 -2
  11. package/pages/CreateClone/index.js +75 -24
  12. package/pages/CreateClone/stores/Main.d.ts +1 -1
  13. package/pages/CreateClone/stores/Main.js +2 -1
  14. package/pages/CreateClone/styles.module.scss +14 -0
  15. package/pages/CreateClone/useForm.d.ts +2 -1
  16. package/pages/CreateClone/useForm.js +3 -2
  17. package/pages/CreateClone/utils/index.d.ts +1 -1
  18. package/pages/CreateClone/utils/index.js +20 -8
  19. package/pages/Instance/Configuration/index.js +3 -1
  20. package/pages/Instance/Info/Disks/Disk/ProgressBar/index.d.ts +2 -1
  21. package/pages/Instance/Info/Disks/Disk/ProgressBar/index.js +1 -1
  22. package/pages/Instance/Info/Disks/{Disk → PoolSection/DatasetRow}/index.d.ts +6 -8
  23. package/pages/Instance/Info/Disks/PoolSection/DatasetRow/index.js +52 -0
  24. package/pages/Instance/Info/Disks/PoolSection/index.d.ts +10 -0
  25. package/pages/Instance/Info/Disks/PoolSection/index.js +64 -0
  26. package/pages/Instance/Info/Disks/index.js +50 -6
  27. package/pages/Instance/Snapshots/components/SnapshotsList/index.js +1 -1
  28. package/types/api/endpoints/createClone.d.ts +1 -1
  29. package/types/api/endpoints/updateClone.d.ts +1 -0
  30. package/types/api/entities/clone.d.ts +7 -0
  31. package/types/api/entities/clone.js +1 -0
  32. package/types/api/entities/instance.d.ts +6 -0
  33. package/types/api/entities/instanceState.d.ts +8 -0
  34. package/types/api/entities/instanceState.js +4 -2
  35. package/pages/Instance/Info/Disks/Disk/Marker/index.d.ts +0 -6
  36. package/pages/Instance/Info/Disks/Disk/Marker/index.js +0 -15
  37. package/pages/Instance/Info/Disks/Disk/index.js +0 -71
@@ -13,6 +13,7 @@ declare type Props = {
13
13
  fullWidth?: TextFieldProps['fullWidth'];
14
14
  disabled?: TextFieldProps['disabled'];
15
15
  error?: boolean;
16
+ style?: TextFieldProps['style'];
16
17
  };
17
18
  export declare const Select: (props: Props) => JSX.Element;
18
19
  export {};
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
2
2
  import { InputProps, TextFieldProps as TextFieldPropsBase } from '@material-ui/core';
3
3
  export declare type TextFieldProps = {
4
4
  label?: string;
@@ -23,5 +23,6 @@ export declare type TextFieldProps = {
23
23
  onFocus?: TextFieldPropsBase['onFocus'];
24
24
  name?: TextFieldPropsBase['name'];
25
25
  helperText?: TextFieldPropsBase['helperText'];
26
+ style?: React.CSSProperties;
26
27
  };
27
28
  export declare const TextField: (props: TextFieldProps) => JSX.Element;
@@ -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 }));
42
+ }, onChange: props.onChange, children: props.children, select: props.select, type: props.type, error: props.error, placeholder: props.placeholder, onBlur: props.onBlur, onFocus: props.onFocus, name: props.name, helperText: props.helperText, style: props.style }));
43
43
  };
@@ -1,3 +1,11 @@
1
1
  export declare const MIN_ENTROPY = 60;
2
2
  export declare function getEntropy(password: string): number;
3
+ /**
4
+ * generates a cryptographically secure random password with guaranteed character diversity
5
+ *
6
+ * @param length - desired password length, constrained to 4-128 characters (default: 16)
7
+ * @returns a random password containing at least one character from each category:
8
+ * lowercase letters, uppercase letters, digits, and special characters (!@$&*_-.)
9
+ */
10
+ export declare function generatePassword(length?: number): string;
3
11
  export declare function validatePassword(password: string, minEntropy: number): string;
@@ -158,6 +158,45 @@ function logPow(expBase, pow, logBase) {
158
158
  }
159
159
  return total;
160
160
  }
161
+ function getSecureRandomInt(max) {
162
+ const array = new Uint32Array(1);
163
+ crypto.getRandomValues(array);
164
+ return array[0] % max;
165
+ }
166
+ /**
167
+ * generates a cryptographically secure random password with guaranteed character diversity
168
+ *
169
+ * @param length - desired password length, constrained to 4-128 characters (default: 16)
170
+ * @returns a random password containing at least one character from each category:
171
+ * lowercase letters, uppercase letters, digits, and special characters (!@$&*_-.)
172
+ */
173
+ export function generatePassword(length = 16) {
174
+ const minLength = 4;
175
+ const maxLength = 128;
176
+ const actualLength = Math.max(Math.min(length, maxLength), minLength);
177
+ const lowercase = 'abcdefghijklmnopqrstuvwxyz';
178
+ const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
179
+ const digits = '0123456789';
180
+ const special = '!@$&*_-.';
181
+ const allChars = lowercase + uppercase + digits + special;
182
+ let password = '';
183
+ // ensure at least one character from each category
184
+ password += lowercase[getSecureRandomInt(lowercase.length)];
185
+ password += uppercase[getSecureRandomInt(uppercase.length)];
186
+ password += digits[getSecureRandomInt(digits.length)];
187
+ password += special[getSecureRandomInt(special.length)];
188
+ // fill the rest with random characters
189
+ for (let i = password.length; i < actualLength; i++) {
190
+ password += allChars[getSecureRandomInt(allChars.length)];
191
+ }
192
+ // shuffle the password to randomize positions (Fisher-Yates)
193
+ const shuffled = password.split('');
194
+ for (let i = shuffled.length - 1; i > 0; i--) {
195
+ const j = getSecureRandomInt(i + 1);
196
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
197
+ }
198
+ return shuffled.join('');
199
+ }
161
200
  export function validatePassword(password, minEntropy) {
162
201
  const entropy = getEntropy(password);
163
202
  if (entropy >= minEntropy) {
@@ -1,40 +1,2 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const PostgresSQLIcon = () => (_jsxs("svg", { className: "postgres-logo", width: "432.071pt", height: "445.383pt", viewBox: "0 0 432.071 445.383", xmlSpace: "preserve", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("g", { id: "orginal", style: {
3
- fillRule: 'nonzero',
4
- clipRule: 'nonzero',
5
- stroke: '#000000',
6
- strokeMiterlimit: 4,
7
- } }), _jsxs("g", { id: "Layer_x0020_3", style: {
8
- fillRule: 'nonzero',
9
- clipRule: 'nonzero',
10
- stroke: '#FFFFFF',
11
- strokeWidth: 12.4651,
12
- strokeLinecap: 'round',
13
- strokeLinejoin: 'round',
14
- strokeMiterlimit: 4,
15
- }, children: [_jsx("path", { style: {
16
- fill: '#000000',
17
- stroke: '#000000',
18
- strokeWidth: 37.3953,
19
- strokeLinecap: 'butt',
20
- strokeLinejoin: 'miter',
21
- strokeMiterlimit: 4,
22
- }, d: "M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322 C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31 c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224 l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107" }), _jsx("path", { style: {
23
- fill: '#336791',
24
- stroke: 'none',
25
- }, d: "M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902 c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416 c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555 c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z" }), _jsx("path", { d: "M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281" }), _jsx("path", { d: "M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335" }), _jsx("path", { d: "M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329" }), _jsx("path", { style: {
26
- strokeLinejoin: 'bevel',
27
- }, d: "M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338 c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762" }), _jsx("path", { d: "M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z" }), _jsx("path", { d: "M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938" }), _jsx("path", { style: {
28
- fill: '#FFFFFF',
29
- stroke: '#FFFFFF',
30
- strokeWidth: 4.155,
31
- strokeLinecap: 'butt',
32
- strokeLinejoin: 'miter',
33
- }, d: "M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z" }), _jsx("path", { style: {
34
- fill: '#FFFFFF',
35
- strokeWidth: 2.0775,
36
- strokeLinecap: 'butt',
37
- strokeLinejoin: 'miter',
38
- }, d: "M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z" }), _jsx("path", { d: "M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435" }), _jsx("path", { style: {
39
- strokeWidth: 3,
40
- }, d: "M0,60.232" })] })] }));
2
+ export const PostgresSQLIcon = () => (_jsxs("svg", { className: "postgres-logo", viewBox: "0 0 128 128", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M93.809 92.112c.785-6.533.55-7.492 5.416-6.433l1.235.108c3.742.17 8.637-.602 11.513-1.938 6.191-2.873 9.861-7.668 3.758-6.409-13.924 2.873-14.881-1.842-14.881-1.842 14.703-21.815 20.849-49.508 15.543-56.287-14.47-18.489-39.517-9.746-39.936-9.52l-.134.025c-2.751-.571-5.83-.912-9.289-.968-6.301-.104-11.082 1.652-14.709 4.402 0 0-44.683-18.409-42.604 23.151.442 8.841 12.672 66.898 27.26 49.362 5.332-6.412 10.484-11.834 10.484-11.834 2.558 1.699 5.622 2.567 8.834 2.255l.249-.212c-.078.796-.044 1.575.099 2.497-3.757 4.199-2.653 4.936-10.166 6.482-7.602 1.566-3.136 4.355-.221 5.084 3.535.884 11.712 2.136 17.238-5.598l-.22.882c1.474 1.18 1.375 8.477 1.583 13.69.209 5.214.558 10.079 1.621 12.948 1.063 2.868 2.317 10.256 12.191 8.14 8.252-1.764 14.561-4.309 15.136-27.985" }), _jsx("path", { d: "M75.458 125.256c-4.367 0-7.211-1.689-8.938-3.32-2.607-2.46-3.641-5.629-4.259-7.522l-.267-.79c-1.244-3.358-1.666-8.193-1.916-14.419-.038-.935-.064-1.898-.093-2.919-.021-.747-.047-1.684-.085-2.664a18.8 18.8 0 01-4.962 1.568c-3.079.526-6.389.356-9.84-.507-2.435-.609-4.965-1.871-6.407-3.82-4.203 3.681-8.212 3.182-10.396 2.453-3.853-1.285-7.301-4.896-10.542-11.037-2.309-4.375-4.542-10.075-6.638-16.943-3.65-11.96-5.969-24.557-6.175-28.693C4.292 23.698 7.777 14.44 15.296 9.129 27.157.751 45.128 5.678 51.68 7.915c4.402-2.653 9.581-3.944 15.433-3.851 3.143.051 6.136.327 8.916.823 2.9-.912 8.628-2.221 15.185-2.139 12.081.144 22.092 4.852 28.949 13.615 4.894 6.252 2.474 19.381.597 26.651-2.642 10.226-7.271 21.102-12.957 30.57 1.544.011 3.781-.174 6.961-.831 6.274-1.295 8.109 2.069 8.607 3.575 1.995 6.042-6.677 10.608-9.382 11.864-3.466 1.609-9.117 2.589-13.745 2.377l-.202-.013-1.216-.107-.12 1.014-.116.991c-.311 11.999-2.025 19.598-5.552 24.619-3.697 5.264-8.835 6.739-13.361 7.709-1.544.33-2.947.474-4.219.474zm-9.19-43.671c2.819 2.256 3.066 6.501 3.287 14.434.028.99.054 1.927.089 2.802.106 2.65.355 8.855 1.327 11.477.137.371.26.747.39 1.146 1.083 3.316 1.626 4.979 6.309 3.978 3.931-.843 5.952-1.599 7.534-3.851 2.299-3.274 3.585-9.86 3.821-19.575l4.783.116-4.75-.57.14-1.186c.455-3.91.783-6.734 3.396-8.602 2.097-1.498 4.486-1.353 6.389-1.01-2.091-1.58-2.669-3.433-2.823-4.193l-.399-1.965 1.121-1.663c6.457-9.58 11.781-21.354 14.609-32.304 2.906-11.251 2.02-17.226 1.134-18.356-11.729-14.987-32.068-8.799-34.192-8.097l-.359.194-1.8.335-.922-.191c-2.542-.528-5.366-.82-8.393-.869-4.756-.08-8.593 1.044-11.739 3.431l-2.183 1.655-2.533-1.043c-5.412-2.213-21.308-6.662-29.696-.721-4.656 3.298-6.777 9.76-6.305 19.207.156 3.119 2.275 14.926 5.771 26.377 4.831 15.825 9.221 21.082 11.054 21.693.32.108 1.15-.537 1.976-1.529a270.708 270.708 0 0110.694-12.07l2.77-2.915 3.349 2.225c1.35.897 2.839 1.406 4.368 1.502l7.987-6.812-1.157 11.808c-.026.265-.039.626.065 1.296l.348 2.238-1.51 1.688-.174.196 4.388 2.025 1.836-2.301z" }), _jsx("path", { fill: "#336791", d: "M115.731 77.44c-13.925 2.873-14.882-1.842-14.882-1.842 14.703-21.816 20.849-49.51 15.545-56.287C101.924.823 76.875 9.566 76.457 9.793l-.135.024c-2.751-.571-5.83-.911-9.291-.967-6.301-.103-11.08 1.652-14.707 4.402 0 0-44.684-18.408-42.606 23.151.442 8.842 12.672 66.899 27.26 49.363 5.332-6.412 10.483-11.834 10.483-11.834 2.559 1.699 5.622 2.567 8.833 2.255l.25-.212c-.078.796-.042 1.575.1 2.497-3.758 4.199-2.654 4.936-10.167 6.482-7.602 1.566-3.136 4.355-.22 5.084 3.534.884 11.712 2.136 17.237-5.598l-.221.882c1.473 1.18 2.507 7.672 2.334 13.557-.174 5.885-.29 9.926.871 13.082 1.16 3.156 2.316 10.256 12.192 8.14 8.252-1.768 12.528-6.351 13.124-13.995.422-5.435 1.377-4.631 1.438-9.49l.767-2.3c.884-7.367.14-9.743 5.225-8.638l1.235.108c3.742.17 8.639-.602 11.514-1.938 6.19-2.871 9.861-7.667 3.758-6.408z" }), _jsx("path", { fill: "#fff", d: "M75.957 122.307c-8.232 0-10.84-6.519-11.907-9.185-1.562-3.907-1.899-19.069-1.551-31.503a1.59 1.59 0 011.64-1.55 1.594 1.594 0 011.55 1.639c-.401 14.341.168 27.337 1.324 30.229 1.804 4.509 4.54 8.453 12.275 6.796 7.343-1.575 10.093-4.359 11.318-11.46.94-5.449 2.799-20.951 3.028-24.01a1.593 1.593 0 011.71-1.472 1.597 1.597 0 011.472 1.71c-.239 3.185-2.089 18.657-3.065 24.315-1.446 8.387-5.185 12.191-13.794 14.037-1.463.313-2.792.453-4 .454zM31.321 90.466a6.71 6.71 0 01-2.116-.35c-5.347-1.784-10.44-10.492-15.138-25.885-3.576-11.717-5.842-23.947-6.041-27.922-.589-11.784 2.445-20.121 9.02-24.778 13.007-9.216 34.888-.44 35.813-.062a1.596 1.596 0 01-1.207 2.955c-.211-.086-21.193-8.492-32.768-.285-5.622 3.986-8.203 11.392-7.672 22.011.167 3.349 2.284 15.285 5.906 27.149 4.194 13.742 8.967 22.413 13.096 23.79.648.216 2.62.873 5.439-2.517A245.272 245.272 0 0145.88 73.046a1.596 1.596 0 012.304 2.208c-.048.05-4.847 5.067-10.077 11.359-2.477 2.979-4.851 3.853-6.786 3.853zm69.429-13.445a1.596 1.596 0 01-1.322-2.487c14.863-22.055 20.08-48.704 15.612-54.414-5.624-7.186-13.565-10.939-23.604-11.156-7.433-.16-13.341 1.738-14.307 2.069l-.243.099c-.971.305-1.716-.227-1.997-.849a1.6 1.6 0 01.631-2.025c.046-.027.192-.089.429-.176l-.021.006.021-.007c1.641-.601 7.639-2.4 15.068-2.315 11.108.118 20.284 4.401 26.534 12.388 2.957 3.779 2.964 12.485.019 23.887-3.002 11.625-8.651 24.118-15.497 34.277-.306.457-.81.703-1.323.703zm.76 10.21c-2.538 0-4.813-.358-6.175-1.174-1.4-.839-1.667-1.979-1.702-2.584-.382-6.71 3.32-7.878 5.208-8.411-.263-.398-.637-.866-1.024-1.349-1.101-1.376-2.609-3.26-3.771-6.078-.182-.44-.752-1.463-1.412-2.648-3.579-6.418-11.026-19.773-6.242-26.612 2.214-3.165 6.623-4.411 13.119-3.716C97.6 28.837 88.5 10.625 66.907 10.271c-6.494-.108-11.82 1.889-15.822 5.93-8.96 9.049-8.636 25.422-8.631 25.586a1.595 1.595 0 11-3.19.084c-.02-.727-.354-17.909 9.554-27.916C53.455 9.272 59.559 6.96 66.96 7.081c13.814.227 22.706 7.25 27.732 13.101 5.479 6.377 8.165 13.411 8.386 15.759.165 1.746-1.088 2.095-1.341 2.147l-.576.013c-6.375-1.021-10.465-.312-12.156 2.104-3.639 5.201 3.406 17.834 6.414 23.229.768 1.376 1.322 2.371 1.576 2.985.988 2.396 2.277 4.006 3.312 5.3.911 1.138 1.7 2.125 1.982 3.283.131.23 1.99 2.98 13.021.703 2.765-.57 4.423-.083 4.93 1.45.997 3.015-4.597 6.532-7.694 7.97-2.775 1.29-7.204 2.106-11.036 2.106zm-4.696-4.021c.35.353 2.101.962 5.727.806 3.224-.138 6.624-.839 8.664-1.786 2.609-1.212 4.351-2.567 5.253-3.492l-.5.092c-7.053 1.456-12.042 1.262-14.828-.577a6.162 6.162 0 01-.54-.401c-.302.119-.581.197-.78.253-1.58.443-3.214.902-2.996 5.105zm-45.562 8.915c-1.752 0-3.596-.239-5.479-.71-1.951-.488-5.24-1.957-5.19-4.37.057-2.707 3.994-3.519 5.476-3.824 5.354-1.103 5.703-1.545 7.376-3.67.488-.619 1.095-1.39 1.923-2.314 1.229-1.376 2.572-2.073 3.992-2.073.989 0 1.8.335 2.336.558 1.708.708 3.133 2.42 3.719 4.467.529 1.847.276 3.625-.71 5.006-3.237 4.533-7.886 6.93-13.443 6.93zm-7.222-4.943c.481.372 1.445.869 2.518 1.137 1.631.408 3.213.615 4.705.615 4.546 0 8.196-1.882 10.847-5.594.553-.774.387-1.757.239-2.274-.31-1.083-1.08-2.068-1.873-2.397-.43-.178-.787-.314-1.115-.314-.176 0-.712 0-1.614 1.009a41.146 41.146 0 00-1.794 2.162c-2.084 2.646-3.039 3.544-9.239 4.821-1.513.31-2.289.626-2.674.835zm12.269-7.36a1.596 1.596 0 01-1.575-1.354 8.218 8.218 0 01-.08-.799c-4.064-.076-7.985-1.82-10.962-4.926-3.764-3.927-5.477-9.368-4.699-14.927.845-6.037.529-11.366.359-14.229-.047-.796-.081-1.371-.079-1.769.003-.505.013-1.844 4.489-4.113 1.592-.807 4.784-2.215 8.271-2.576 5.777-.597 9.585 1.976 10.725 7.246 3.077 14.228.244 20.521-1.825 25.117-.385.856-.749 1.664-1.04 2.447l-.257.69c-1.093 2.931-2.038 5.463-1.748 7.354a1.595 1.595 0 01-1.335 1.819l-.244.02zM42.464 42.26l.062 1.139c.176 2.974.504 8.508-.384 14.86-.641 4.585.759 9.06 3.843 12.276 2.437 2.542 5.644 3.945 8.94 3.945h.068c.369-1.555.982-3.197 1.642-4.966l.255-.686c.329-.884.714-1.74 1.122-2.646 1.991-4.424 4.47-9.931 1.615-23.132-.565-2.615-1.936-4.128-4.189-4.627-4.628-1.022-11.525 2.459-12.974 3.837zm9.63-.677c-.08.564 1.033 2.07 2.485 2.271 1.449.203 2.689-.975 2.768-1.539.079-.564-1.033-1.186-2.485-1.388-1.451-.202-2.691.092-2.768.656zm2.818 2.826l-.407-.028c-.9-.125-1.81-.692-2.433-1.518-.219-.29-.576-.852-.505-1.354.101-.736.999-1.177 2.4-1.177.313 0 .639.023.967.069.766.106 1.477.327 2.002.62.91.508.977 1.075.936 1.368-.112.813-1.405 2.02-2.96 2.02zm-2.289-2.732c.045.348.907 1.496 2.029 1.651l.261.018c1.036 0 1.81-.815 1.901-1.082-.096-.182-.762-.634-2.025-.81a5.823 5.823 0 00-.821-.059c-.812 0-1.243.183-1.345.282zm43.605-1.245c.079.564-1.033 2.07-2.484 2.272-1.45.202-2.691-.975-2.771-1.539-.076-.564 1.036-1.187 2.486-1.388 1.45-.203 2.689.092 2.769.655zm-2.819 2.56c-1.396 0-2.601-1.086-2.7-1.791-.115-.846 1.278-1.489 2.712-1.688.316-.044.629-.066.93-.066 1.238 0 2.058.363 2.14.949.053.379-.238.964-.739 1.492-.331.347-1.026.948-1.973 1.079l-.37.025zm.943-3.013c-.276 0-.564.021-.856.061-1.441.201-2.301.779-2.259 1.089.048.341.968 1.332 2.173 1.332l.297-.021c.787-.109 1.378-.623 1.66-.919.443-.465.619-.903.598-1.052-.028-.198-.56-.49-1.613-.49zm3.965 32.843a1.594 1.594 0 01-1.324-2.483c3.398-5.075 2.776-10.25 2.175-15.255-.257-2.132-.521-4.337-.453-6.453.07-2.177.347-3.973.614-5.71.317-2.058.617-4.002.493-6.31a1.595 1.595 0 113.186-.172c.142 2.638-.197 4.838-.525 6.967-.253 1.643-.515 3.342-.578 5.327-.061 1.874.178 3.864.431 5.97.64 5.322 1.365 11.354-2.691 17.411a1.596 1.596 0 01-1.328.708z" })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postgres.ai/shared",
3
- "version": "4.0.2-pr-1061.1",
3
+ "version": "4.0.2-pr-1118",
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "peerDependencies": {
@@ -24,7 +24,7 @@
24
24
  "@types/react-router-dom": "^5.3.1",
25
25
  "@types/react-syntax-highlighter": "^15.5.6",
26
26
  "classnames": "^2.3.1",
27
- "clsx": "^1.1.1",
27
+ "clsx": "^2.1.1",
28
28
  "copy-to-clipboard": "^3.3.1",
29
29
  "create-file-webpack": "^1.0.2",
30
30
  "crypto-browserify": "^3.12.0",
@@ -9,7 +9,8 @@ import { useEffect, useState } from 'react';
9
9
  import { observer } from 'mobx-react-lite';
10
10
  import { useHistory } from 'react-router-dom';
11
11
  import copyToClipboard from 'copy-to-clipboard';
12
- import { makeStyles, Button, FormControlLabel, Checkbox, TextField, IconButton, } from '@material-ui/core';
12
+ import { makeStyles, Button, TextField, IconButton, } from '@material-ui/core';
13
+ import { Select } from '@postgres.ai/shared/components/Select';
13
14
  import { getSshPortForwardingCommand, getPsqlConnectionStr, getJdbcConnectionStr, } from '@postgres.ai/shared/utils/connection';
14
15
  import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
15
16
  import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub';
@@ -163,7 +164,7 @@ const useStyles = makeStyles((theme) => ({
163
164
  },
164
165
  }), { index: 1 });
165
166
  export const Clone = observer((props) => {
166
- var _a, _b, _c, _d, _e, _f, _g;
167
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
167
168
  const classes = useStyles();
168
169
  const history = useHistory();
169
170
  const stores = useCreatedStores(props);
@@ -207,8 +208,45 @@ export const Clone = observer((props) => {
207
208
  };
208
209
  // Clone reload.
209
210
  const reloadClone = () => stores.main.reload();
210
- // Data protection.
211
- const toggleDataProtection = () => stores.main.updateClone(!clone.protected);
211
+ // Data protection - build options based on admin config.
212
+ const maxDurationMinutes = (_g = (_f = (_e = instance.state) === null || _e === void 0 ? void 0 : _e.cloning) === null || _f === void 0 ? void 0 : _f.protectionMaxDurationMinutes) !== null && _g !== void 0 ? _g : 0;
213
+ const allowForever = maxDurationMinutes === 0;
214
+ const allDurationOptions = [
215
+ { value: '60', minutes: 60, children: '1 hour' },
216
+ { value: '720', minutes: 720, children: '12 hours' },
217
+ { value: '1440', minutes: 1440, children: '1 day' },
218
+ { value: '2880', minutes: 2880, children: '2 days' },
219
+ { value: '4320', minutes: 4320, children: '3 days' },
220
+ { value: '5760', minutes: 5760, children: '4 days' },
221
+ { value: '7200', minutes: 7200, children: '5 days' },
222
+ { value: '8640', minutes: 8640, children: '6 days' },
223
+ { value: '10080', minutes: 10080, children: '7 days' },
224
+ { value: '20160', minutes: 20160, children: '14 days' },
225
+ { value: '43200', minutes: 43200, children: '30 days' },
226
+ ];
227
+ const protectionOptions = [
228
+ { value: 'none', children: 'No protection' },
229
+ ...allDurationOptions
230
+ .filter((opt) => maxDurationMinutes === 0 || opt.minutes <= maxDurationMinutes)
231
+ .map(({ value, children }) => ({ value, children })),
232
+ ...(allowForever ? [{ value: '0', children: 'Forever' }] : []),
233
+ ];
234
+ const handleProtectionChange = (e) => {
235
+ const value = e.target.value;
236
+ if (value === 'none') {
237
+ stores.main.updateCloneProtection(null);
238
+ }
239
+ else {
240
+ stores.main.updateCloneProtection(parseInt(value, 10));
241
+ }
242
+ };
243
+ const getCurrentProtectionValue = () => {
244
+ if (!clone.protected)
245
+ return 'none';
246
+ if (!clone.protectedTillDate)
247
+ return '0';
248
+ return 'current';
249
+ };
212
250
  // Commands.
213
251
  const sshPortForwardingUrl = getSshPortForwardingCommand(instance, clone);
214
252
  const jdbcConnectionStr = getJdbcConnectionStr(clone);
@@ -221,7 +259,7 @@ export const Clone = observer((props) => {
221
259
  isUpdatingClone ||
222
260
  !isCloneStable;
223
261
  return (_jsxs(_Fragment, { children: [headRendered, _jsxs("div", { className: classes.wrapper, children: [_jsxs("div", { className: classes.summary, children: [_jsxs("div", { className: classes.actions, children: [_jsxs(Button, { variant: "contained", color: "primary", onClick: requestResetClone, disabled: isDisabledControls, title: 'Reset clone', className: classes.actionButton, children: ["Reset clone", isResettingClone && (_jsx(Spinner, { size: "sm", className: classes.spinner }))] }), _jsxs(Button, { variant: "contained", color: "primary", onClick: requestDestroyClone, disabled: isDisabledControls, title: 'Delete this clone', className: classes.actionButton, children: ["Delete clone", isDestroyingClone && (_jsx(Spinner, { size: "sm", className: classes.spinner }))] }), !props.hideBranchingFeatures && _jsxs(Button, { variant: "contained", color: "primary", onClick: createSnapshot, disabled: isDisabledControls, title: 'Create snapshot', className: classes.actionButton, children: ["Create snapshot", snapshots.snapshotDataLoading && (_jsx(Spinner, { size: "sm", className: classes.spinner }))] }), _jsxs(Button, { variant: "outlined", color: "secondary", onClick: reloadClone, disabled: isDisabledControls, title: 'Refresh clone information', className: classes.actionButton, children: ["Reload info", isReloading && _jsx(Spinner, { size: "sm", className: classes.spinner })] })] }), stores.main.destroyCloneError ||
224
- (stores.main.resetCloneError && (_jsx(ErrorStub, { title: 'Resetting error', message: stores.main.resetCloneError, className: classes.errorStub }))), !props.hideBranchingFeatures && _jsxs("div", { children: [_jsx("p", { children: _jsx("strong", { children: "Branch" }) }), _jsx("p", { className: classes.text, children: clone.branch })] }), _jsxs("div", { className: classes.title, children: [_jsx("p", { children: _jsx("strong", { children: "Created" }) }), _jsx("p", { className: classes.text, children: clone.createdAt })] }), _jsx("br", {}), _jsxs("div", { children: [_jsxs("p", { children: [_jsx("strong", { children: "Data state at" }), "\u00A0", _jsx(Tooltip, { content: _jsxs(_Fragment, { children: [_jsx("strong", { children: "Data state time" }), " is a time at which data is\u00A0 recovered for this clone."] }), children: icons.infoIcon })] }), _jsx("p", { className: classes.text, children: (_e = clone.snapshot) === null || _e === void 0 ? void 0 : _e.dataStateAt })] }), _jsx("br", {}), _jsxs("div", { children: [_jsx("p", { children: _jsx("strong", { children: "Status" }) }), _jsx(Status, { rawClone: clone, className: classes.status })] }), _jsx("br", {}), _jsxs("div", { children: [_jsxs("p", { children: [_jsx("strong", { children: "Summary" }), "\u00A0", _jsx(Tooltip, { content: _jsxs(_Fragment, { children: [_jsx("strong", { children: "Logical data size" }), " is a logical size of files in PGDATA which is being thin-cloned.", _jsx("br", {}), _jsx("br", {}), _jsx("strong", { children: "Physical data diff size" }), " is an actual size of a\u00A0 thin clone. On creation there is no diff between clone\u2019s\u00A0 and initial data state, all data blocks match. During work\u00A0 with the clone data diverges and data diff size increases.", _jsx("br", {}), _jsx("br", {}), _jsx("strong", { children: "Clone creation time" }), " is time which was\u00A0 spent to provision the clone."] }), children: icons.infoIcon })] }), _jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Logical data size:" }), ((_f = instance.state) === null || _f === void 0 ? void 0 : _f.dataSize)
262
+ (stores.main.resetCloneError && (_jsx(ErrorStub, { title: 'Resetting error', message: stores.main.resetCloneError, className: classes.errorStub }))), !props.hideBranchingFeatures && _jsxs("div", { children: [_jsx("p", { children: _jsx("strong", { children: "Branch" }) }), _jsx("p", { className: classes.text, children: clone.branch })] }), _jsxs("div", { className: classes.title, children: [_jsx("p", { children: _jsx("strong", { children: "Created" }) }), _jsx("p", { className: classes.text, children: clone.createdAt })] }), _jsx("br", {}), _jsxs("div", { children: [_jsxs("p", { children: [_jsx("strong", { children: "Data state at" }), "\u00A0", _jsx(Tooltip, { content: _jsxs(_Fragment, { children: [_jsx("strong", { children: "Data state time" }), " is a time at which data is\u00A0 recovered for this clone."] }), children: icons.infoIcon })] }), _jsx("p", { className: classes.text, children: (_h = clone.snapshot) === null || _h === void 0 ? void 0 : _h.dataStateAt })] }), _jsx("br", {}), _jsxs("div", { children: [_jsx("p", { children: _jsx("strong", { children: "Status" }) }), _jsx(Status, { rawClone: clone, className: classes.status })] }), _jsx("br", {}), _jsxs("div", { children: [_jsxs("p", { children: [_jsx("strong", { children: "Summary" }), "\u00A0", _jsx(Tooltip, { content: _jsxs(_Fragment, { children: [_jsx("strong", { children: "Logical data size" }), " is a logical size of files in PGDATA which is being thin-cloned.", _jsx("br", {}), _jsx("br", {}), _jsx("strong", { children: "Physical data diff size" }), " is an actual size of a\u00A0 thin clone. On creation there is no diff between clone\u2019s\u00A0 and initial data state, all data blocks match. During work\u00A0 with the clone data diverges and data diff size increases.", _jsx("br", {}), _jsx("br", {}), _jsx("strong", { children: "Clone creation time" }), " is time which was\u00A0 spent to provision the clone."] }), children: icons.infoIcon })] }), _jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Logical data size:" }), ((_j = instance.state) === null || _j === void 0 ? void 0 : _j.dataSize)
225
263
  ? formatBytesIEC(instance.state.dataSize)
226
264
  : '-'] }), _jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Physical data diff size:" }), clone.metadata.cloneDiffSize
227
265
  ? formatBytesIEC(clone.metadata.cloneDiffSize)
@@ -248,5 +286,10 @@ export const Clone = observer((props) => {
248
286
  style: styles.inputFieldLabel,
249
287
  }, FormHelperTextProps: {
250
288
  style: styles.inputFieldHelper,
251
- } }), _jsx(IconButton, { className: classes.copyButton, "aria-label": "Copy", onClick: () => copyToClipboard(jdbcConnectionStr), children: icons.copyIcon })] }), "\u00A0", _jsx(Tooltip, { content: _jsx(_Fragment, { children: "Used to connect to PostgreSQL using JDBC. Change DBNAME to the name of the database you want to connect to, and change DBPASSWORD to the password you used when creating the clone." }), children: _jsx("span", { className: classes.textFieldInfo, children: icons.infoIcon }) })] }))] })), _jsx("br", {}), _jsx("div", { className: classes.fieldBlock, children: _jsxs("span", { className: classes.remark, children: ["Password was set during clone creation. It\u2019s not being stored.", _jsx("br", {}), "You would need to recreate a clone if the password is lost."] }) }), _jsx("br", {}), _jsx("p", { children: _jsx("strong", { children: "Protection" }) }), _jsxs("p", { children: [_jsx(FormControlLabel, { className: classes.checkboxLabel, control: _jsx(Checkbox, { checked: clone.protected, onChange: toggleDataProtection, name: "protected", disabled: isDisabledControls }), label: "Enable deletion protection" }), _jsx("br", {}), _jsxs("span", { className: classes.remark, children: ["When enabled, no one can delete this clone and automated deletion is also disabled.", _jsx("br", {}), "Please be careful: abandoned clones with this checkbox enabled may cause out-of-disk-space events. Check disk space on a daily basis and delete this clone once your work is done."] })] }), stores.main.updateCloneError && (_jsx(ErrorStub, { title: "Updating error", message: stores.main.updateCloneError, className: classes.errorStub }))] }), _jsxs("div", { className: classes.snippetContainer, children: [_jsx(SectionTitle, { tag: "h2", level: 2, text: 'Reset clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can reset the clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCliResetCloneCommand(props.cloneId) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Delete clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can delete the clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCliDestroyCloneCommand(props.cloneId) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Toggle deletion protection using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can toggle deletion protection using CLI for this clone using the following command:" }), _jsx(SyntaxHighlight, { content: getCliProtectedCloneCommand(true) }), _jsx(SyntaxHighlight, { content: getCliProtectedCloneCommand(false) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Create snapshot for this clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can create a snapshot for this clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCreateSnapshotCommand(props.cloneId) })] }), _jsxs(_Fragment, { children: [_jsx(DestroyCloneRestrictionModal, { isOpen: isOpenRestrictionModal, onClose: () => setIsOpenRestrictionModal(false), cloneId: clone.id }), _jsx(DestroyCloneModal, { isOpen: isOpenDestroyModal, onClose: () => setIsOpenDestroyModal(false), cloneId: clone.id, onDestroyClone: destroyClone }), _jsx(ResetCloneModal, { isOpen: isOpenResetModal, onClose: () => setIsOpenResetModal(false), clone: clone, snapshots: snapshots.data, onResetClone: resetClone, version: (_g = instance.state) === null || _g === void 0 ? void 0 : _g.engine.version })] })] })] }));
289
+ } }), _jsx(IconButton, { className: classes.copyButton, "aria-label": "Copy", onClick: () => copyToClipboard(jdbcConnectionStr), children: icons.copyIcon })] }), "\u00A0", _jsx(Tooltip, { content: _jsx(_Fragment, { children: "Used to connect to PostgreSQL using JDBC. Change DBNAME to the name of the database you want to connect to, and change DBPASSWORD to the password you used when creating the clone." }), children: _jsx("span", { className: classes.textFieldInfo, children: icons.infoIcon }) })] }))] })), _jsx("br", {}), _jsx("div", { className: classes.fieldBlock, children: _jsxs("span", { className: classes.remark, children: ["Password was set during clone creation. It\u2019s not being stored.", _jsx("br", {}), "You would need to recreate a clone if the password is lost."] }) }), _jsx("br", {}), _jsx("p", { children: _jsx("strong", { children: "Protection" }) }), _jsxs("div", { style: { marginTop: '8px', marginBottom: '16px' }, children: [_jsx(Select, { label: "Deletion protection", items: clone.protected && clone.protectedTillDate
290
+ ? [
291
+ { value: 'current', children: `Protected until ${clone.protectedTillDate.toLocaleString()}` },
292
+ ...protectionOptions,
293
+ ]
294
+ : protectionOptions, value: getCurrentProtectionValue(), onChange: handleProtectionChange, disabled: isDisabledControls, style: { minWidth: 100 } }), isUpdatingClone && _jsx(Spinner, { size: "sm", className: classes.spinner })] }), _jsxs("span", { className: classes.remark, children: ["Select how long this clone should be protected from deletion. Protected clones cannot be deleted manually or automatically.", _jsx("br", {}), "Check disk space regularly and delete this clone once your work is done."] }), stores.main.updateCloneError && (_jsx(ErrorStub, { title: "Updating error", message: stores.main.updateCloneError, className: classes.errorStub }))] }), _jsxs("div", { className: classes.snippetContainer, children: [_jsx(SectionTitle, { tag: "h2", level: 2, text: 'Reset clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can reset the clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCliResetCloneCommand(props.cloneId) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Delete clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can delete the clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCliDestroyCloneCommand(props.cloneId) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Toggle deletion protection using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can toggle deletion protection using CLI for this clone using the following command:" }), _jsx(SyntaxHighlight, { content: getCliProtectedCloneCommand(true) }), _jsx(SyntaxHighlight, { content: getCliProtectedCloneCommand(false) }), _jsx(SectionTitle, { className: classes.title, tag: "h2", level: 2, text: 'Create snapshot for this clone using CLI' }), _jsx("p", { className: classes.tooltip, children: "You can create a snapshot for this clone using CLI using the following command:" }), _jsx(SyntaxHighlight, { content: getCreateSnapshotCommand(props.cloneId) })] }), _jsxs(_Fragment, { children: [_jsx(DestroyCloneRestrictionModal, { isOpen: isOpenRestrictionModal, onClose: () => setIsOpenRestrictionModal(false), cloneId: clone.id }), _jsx(DestroyCloneModal, { isOpen: isOpenDestroyModal, onClose: () => setIsOpenDestroyModal(false), cloneId: clone.id, onDestroyClone: destroyClone }), _jsx(ResetCloneModal, { isOpen: isOpenResetModal, onClose: () => setIsOpenResetModal(false), clone: clone, snapshots: snapshots.data, onResetClone: resetClone, version: (_k = instance.state) === null || _k === void 0 ? void 0 : _k.engine.version })] })] })] }));
252
295
  });
@@ -42,6 +42,6 @@ export declare class MainStore {
42
42
  private loadClone;
43
43
  resetClone: (snapshotId: string) => Promise<boolean>;
44
44
  destroyClone: () => Promise<boolean>;
45
- updateClone: (isProtected: boolean) => Promise<void>;
45
+ updateCloneProtection: (durationMinutes: number | null) => Promise<void>;
46
46
  }
47
47
  export {};
@@ -110,21 +110,27 @@ export class MainStore {
110
110
  this.isDestroyingClone = false;
111
111
  return Boolean(response);
112
112
  };
113
- this.updateClone = async (isProtected) => {
113
+ this.updateCloneProtection = async (durationMinutes) => {
114
114
  if (!this.instance || !this.clone)
115
115
  return;
116
116
  this.isUpdatingClone = true;
117
117
  const prevIsProtected = this.clone.protected;
118
+ const isProtected = durationMinutes !== null;
118
119
  this.clone.protected = isProtected;
119
120
  const { response, error } = await this.api.updateClone({
120
121
  instanceId: this.instance.id,
121
122
  cloneId: this.clone.id,
122
123
  clone: {
123
124
  isProtected,
125
+ protectionDurationMinutes: durationMinutes !== null && durationMinutes !== void 0 ? durationMinutes : undefined,
124
126
  },
125
127
  });
126
- if (!response)
128
+ if (response) {
129
+ await this.loadClone(this.instance.id, this.clone.id);
130
+ }
131
+ else {
127
132
  this.clone.protected = prevIsProtected;
133
+ }
128
134
  if (error)
129
135
  this.updateCloneError = await getTextFromUnknownApiError(error);
130
136
  this.isUpdatingClone = false;
@@ -4,19 +4,21 @@ import { useEffect, useState } from 'react';
4
4
  import { useHistory } from 'react-router-dom';
5
5
  import { observer } from 'mobx-react-lite';
6
6
  import { useTimer } from 'use-timer';
7
- import { Paper, FormControlLabel, Checkbox } from '@material-ui/core';
8
- import { Info as InfoIcon } from '@material-ui/icons';
7
+ import { Paper, IconButton, InputAdornment } from '@material-ui/core';
8
+ import { Info as InfoIcon, Visibility, VisibilityOff } from '@material-ui/icons';
9
+ import copy from 'copy-to-clipboard';
9
10
  import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex';
10
11
  import { TextField } from '@postgres.ai/shared/components/TextField';
11
12
  import { Select } from '@postgres.ai/shared/components/Select';
12
13
  import { Button } from '@postgres.ai/shared/components/Button';
13
14
  import { Spinner } from '@postgres.ai/shared/components/Spinner';
14
15
  import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub';
16
+ import { Tooltip } from '@postgres.ai/shared/components/Tooltip';
15
17
  import { round } from '@postgres.ai/shared/utils/numbers';
16
18
  import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
17
19
  import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle';
18
20
  import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight';
19
- import { MIN_ENTROPY, getEntropy, validatePassword, } from '@postgres.ai/shared/helpers/getEntropy';
21
+ import { MIN_ENTROPY, getEntropy, validatePassword, generatePassword, } from '@postgres.ai/shared/helpers/getEntropy';
20
22
  import { useCreatedStores } from './useCreatedStores';
21
23
  import { useForm } from './useForm';
22
24
  import { getCliCloneStatus, getCliCreateCloneCommand } from './utils';
@@ -31,6 +33,9 @@ export const CreateClone = observer((props) => {
31
33
  const [branchesList, setBranchesList] = useState([]);
32
34
  const [snapshots, setSnapshots] = useState([]);
33
35
  const [isLoadingSnapshots, setIsLoadingSnapshots] = useState(false);
36
+ const [selectedBranchKey, setSelectedBranchKey] = useState('');
37
+ const [showPassword, setShowPassword] = useState(false);
38
+ const [passwordGenerated, setPasswordGenerated] = useState(false);
34
39
  // Form.
35
40
  const onSubmit = async (values) => {
36
41
  if (!values.dbPassword || getEntropy(values.dbPassword) < MIN_ENTROPY) {
@@ -45,38 +50,50 @@ export const CreateClone = observer((props) => {
45
50
  timer.reset();
46
51
  }
47
52
  };
48
- const fetchBranchSnapshotsData = async (branchName, initialSnapshotId) => {
53
+ const fetchBranchSnapshotsData = async (branchName, dataset, initialSnapshotId) => {
49
54
  var _a;
50
- const snapshotsRes = (_a = (await stores.main.getSnapshots(props.instanceId, branchName))) !== null && _a !== void 0 ? _a : [];
55
+ const snapshotsRes = (_a = (await stores.main.getSnapshots(props.instanceId, branchName, dataset))) !== null && _a !== void 0 ? _a : [];
51
56
  setSnapshots(snapshotsRes);
52
57
  const selectedSnapshot = snapshotsRes.find(s => s.id === initialSnapshotId) || snapshotsRes[0];
53
58
  formik.setFieldValue('snapshotId', selectedSnapshot === null || selectedSnapshot === void 0 ? void 0 : selectedSnapshot.id);
54
59
  };
55
60
  const handleSelectBranch = async (e) => {
56
- const selectedBranch = e.target.value;
57
- formik.setFieldValue('branch', selectedBranch);
61
+ const compositeKey = e.target.value;
62
+ const [branchName, dataset] = compositeKey.split('|');
63
+ setSelectedBranchKey(compositeKey);
64
+ formik.setFieldValue('branch', branchName);
65
+ formik.setFieldValue('dataset', dataset);
58
66
  if (props.api.getSnapshots) {
59
- await fetchBranchSnapshotsData(selectedBranch);
67
+ await fetchBranchSnapshotsData(branchName, dataset);
60
68
  }
61
69
  };
62
70
  const formik = useForm(onSubmit);
63
71
  const fetchData = async (initialBranch, initialSnapshotId) => {
64
- var _a, _b, _c, _d, _e;
72
+ var _a, _b, _c, _d, _e, _f;
65
73
  try {
66
74
  setIsLoadingSnapshots(true);
67
75
  await stores.main.load(props.instanceId);
68
76
  const branches = (_a = (await stores.main.getBranches(props.instanceId))) !== null && _a !== void 0 ? _a : [];
69
- let initiallySelectedBranch = (_b = branches[0]) === null || _b === void 0 ? void 0 : _b.name;
70
- if (initialBranch && branches.find((branch) => branch.name === initialBranch)) {
71
- initiallySelectedBranch = initialBranch;
77
+ const mainBranch = branches.find((branch) => branch.name === 'main');
78
+ let initiallySelectedBranch = mainBranch !== null && mainBranch !== void 0 ? mainBranch : branches[0];
79
+ if (initialBranch) {
80
+ const foundBranch = branches.find((branch) => branch.name === initialBranch);
81
+ if (foundBranch) {
82
+ initiallySelectedBranch = foundBranch;
83
+ }
72
84
  }
73
- setBranchesList(branches.map((branch) => branch.name));
74
- formik.setFieldValue('branch', initiallySelectedBranch);
75
- if (props.api.getSnapshots) {
76
- await fetchBranchSnapshotsData(initiallySelectedBranch, initialSnapshotId);
85
+ setBranchesList(branches);
86
+ formik.setFieldValue('branch', (_b = initiallySelectedBranch === null || initiallySelectedBranch === void 0 ? void 0 : initiallySelectedBranch.name) !== null && _b !== void 0 ? _b : '');
87
+ formik.setFieldValue('dataset', (_c = initiallySelectedBranch === null || initiallySelectedBranch === void 0 ? void 0 : initiallySelectedBranch.baseDataset) !== null && _c !== void 0 ? _c : '');
88
+ if (initiallySelectedBranch) {
89
+ const compositeKey = `${initiallySelectedBranch.name}|${initiallySelectedBranch.baseDataset}`;
90
+ setSelectedBranchKey(compositeKey);
77
91
  }
78
- else {
79
- const allSnapshots = (_e = (_d = (_c = stores.main) === null || _c === void 0 ? void 0 : _c.snapshots) === null || _d === void 0 ? void 0 : _d.data) !== null && _e !== void 0 ? _e : [];
92
+ if (props.api.getSnapshots && initiallySelectedBranch) {
93
+ await fetchBranchSnapshotsData(initiallySelectedBranch.name, initiallySelectedBranch.baseDataset, initialSnapshotId);
94
+ }
95
+ else if (!props.api.getSnapshots) {
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 : [];
80
97
  const sortedSnapshots = allSnapshots.slice().sort(compareSnapshotsDesc);
81
98
  setSnapshots(sortedSnapshots);
82
99
  let selectedSnapshot = allSnapshots.find(s => s.id === initialSnapshotId) || allSnapshots[0];
@@ -120,10 +137,10 @@ export const CreateClone = observer((props) => {
120
137
  ((_f = (_e = stores.main) === null || _e === void 0 ? void 0 : _e.snapshots) === null || _f === void 0 ? void 0 : _f.error) })] }));
121
138
  const isCloneUnstable = Boolean(stores.main.clone && !stores.main.isCloneStable);
122
139
  const isCreatingClone = (formik.isSubmitting || isCloneUnstable) && !stores.main.cloneError;
123
- return (_jsxs(_Fragment, { children: [headRendered, _jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.form, children: [_jsxs("div", { className: styles.section, children: [branchesList && branchesList.length > 0 && (_jsx(Select, { fullWidth: true, label: "Branch", value: formik.values.branch, disabled: !branchesList || isCreatingClone, onChange: handleSelectBranch, error: Boolean(formik.errors.branch), items: (_g = branchesList === null || branchesList === void 0 ? void 0 : branchesList.map((snapshot) => {
140
+ return (_jsxs(_Fragment, { children: [headRendered, _jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.form, children: [_jsxs("div", { className: styles.section, children: [branchesList && branchesList.length > 0 && (_jsx(Select, { fullWidth: true, label: "Branch", value: selectedBranchKey, disabled: !branchesList || isCreatingClone, onChange: handleSelectBranch, error: Boolean(formik.errors.branch), items: (_g = branchesList === null || branchesList === void 0 ? void 0 : branchesList.map((branch) => {
124
141
  return {
125
- value: snapshot,
126
- children: snapshot,
142
+ value: `${branch.name}|${branch.baseDataset}`,
143
+ children: `${branch.name} (${branch.baseDataset})`,
127
144
  };
128
145
  })) !== null && _g !== void 0 ? _g : [] })), _jsx(TextField, { fullWidth: true, label: "Clone ID", value: formik.values.cloneId, onChange: (e) => {
129
146
  const sanitizedCloneIdValue = e.target.value.replace(/\s/g, '');
@@ -134,12 +151,46 @@ export const CreateClone = observer((props) => {
134
151
  value: snapshot.id,
135
152
  children: (_jsxs("div", { className: styles.snapshotItem, children: [_jsxs("strong", { className: styles.snapshotOverflow, children: [snapshot === null || snapshot === void 0 ? void 0 : snapshot.id, " ", isLatest && _jsx("span", { children: "Latest" })] }), (snapshot === null || snapshot === void 0 ? void 0 : snapshot.dataStateAt) && (_jsxs("p", { children: ["Data state at: ", snapshot === null || snapshot === void 0 ? void 0 : snapshot.dataStateAt] })), snapshot.message && (_jsxs("span", { children: ["Message: ", snapshot.message] }))] })),
136
153
  };
137
- })) !== null && _h !== void 0 ? _h : [] }), _jsx("p", { className: styles.remark, children: "By default latest snapshot of database is used. You can select\u00A0 different snapshots if earlier database state is needed." })] }), _jsxs("div", { className: styles.section, children: [_jsx("h2", { className: styles.title, children: "Database credentials *" }), _jsx("p", { className: styles.text, children: "Set custom credentials for the new clone. Save the password in reliable place, it can't be read later." }), _jsx(TextField, { fullWidth: true, label: "Database username *", value: formik.values.dbUser, onChange: (e) => formik.setFieldValue('dbUser', e.target.value), error: Boolean(formik.errors.dbUser), disabled: isCreatingClone }), _jsx(TextField, { fullWidth: true, label: "Database password *", type: "password", value: formik.values.dbPassword, onChange: (e) => {
154
+ })) !== null && _h !== void 0 ? _h : [] }), _jsx("p", { className: styles.remark, children: "By default latest snapshot of database is used. You can select\u00A0 different snapshots if earlier database state is needed." })] }), _jsxs("div", { className: styles.section, children: [_jsx("h2", { className: styles.title, children: "Database credentials *" }), _jsx("p", { className: styles.text, children: "Set custom credentials for the new clone. Save the password in reliable place, it can't be read later." }), _jsx(TextField, { fullWidth: true, label: "Database username *", value: formik.values.dbUser, onChange: (e) => formik.setFieldValue('dbUser', e.target.value), error: Boolean(formik.errors.dbUser), disabled: isCreatingClone }), _jsx(TextField, { fullWidth: true, label: "Database password *", type: showPassword ? 'text' : 'password', value: formik.values.dbPassword, onChange: (e) => {
138
155
  formik.setFieldValue('dbPassword', e.target.value);
156
+ setPasswordGenerated(false);
139
157
  if (formik.errors.dbPassword) {
140
158
  formik.setFieldError('dbPassword', '');
141
159
  }
142
- }, error: Boolean(formik.errors.dbPassword), disabled: isCreatingClone }), _jsx("p", { className: cn(formik.errors.dbPassword && styles.error, styles.remark), children: formik.errors.dbPassword })] }), _jsxs("div", { className: styles.form, children: [_jsx(FormControlLabel, { label: "Enable deletion protection", control: _jsx(Checkbox, { checked: formik.values.isProtected, onChange: (e) => formik.setFieldValue('isProtected', e.target.checked), name: "protected", disabled: isCreatingClone }) }), _jsxs("p", { className: styles.remark, children: ["When enabled, no one can delete this clone and automated deletion is also disabled.", _jsx("br", {}), "Please be careful: abandoned clones with this checkbox enabled may cause out-of-disk-space events. Check disk space on a daily basis and delete this clone once the work is done."] })] }), _jsxs("div", { className: cn(styles.marginBottom, styles.section), children: [_jsxs(Paper, { className: styles.summary, children: [_jsx(InfoIcon, { className: styles.summaryIcon }), _jsxs("div", { className: styles.params, children: [_jsxs("p", { className: styles.param, children: [_jsx("span", { children: "Data size:" }), _jsx("strong", { children: ((_j = stores.main.instance.state) === null || _j === void 0 ? void 0 : _j.dataSize)
160
+ }, error: Boolean(formik.errors.dbPassword), disabled: isCreatingClone, InputProps: {
161
+ endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(Tooltip, { content: showPassword ? 'Hide password' : 'Show password', children: _jsx(IconButton, { size: "small", onClick: () => setShowPassword(!showPassword), disabled: isCreatingClone, style: { marginRight: 4 }, children: showPassword ? _jsx(Visibility, { fontSize: "small" }) : _jsx(VisibilityOff, { fontSize: "small" }) }) }) })),
162
+ } }), _jsxs("div", { className: styles.passwordActions, children: [_jsx(Button, { variant: "secondary", size: "small", onClick: () => copy(formik.values.dbPassword), isDisabled: isCreatingClone || !formik.values.dbPassword, children: "Copy" }), _jsx(Button, { variant: "secondary", size: "small", onClick: () => {
163
+ const newPassword = generatePassword(16);
164
+ formik.setFieldValue('dbPassword', newPassword);
165
+ setPasswordGenerated(true);
166
+ if (formik.errors.dbPassword) {
167
+ formik.setFieldError('dbPassword', '');
168
+ }
169
+ }, isDisabled: isCreatingClone, children: "Generate" }), passwordGenerated && (_jsx("span", { className: styles.passwordHint, children: "New password created. Copy and save it securely." }))] }), _jsx("p", { className: cn(formik.errors.dbPassword && styles.error, styles.remark), children: formik.errors.dbPassword })] }), _jsxs("div", { className: styles.section, children: [_jsx("h2", { className: styles.title, children: "Clone protection" }), _jsx(Select, { label: "Deletion protection", items: (() => {
170
+ var _a, _b, _c, _d;
171
+ const maxDurationMinutes = (_d = (_c = (_b = (_a = stores.main.instance) === null || _a === void 0 ? void 0 : _a.state) === null || _b === void 0 ? void 0 : _b.cloning) === null || _c === void 0 ? void 0 : _c.protectionMaxDurationMinutes) !== null && _d !== void 0 ? _d : 0;
172
+ const allowForever = maxDurationMinutes === 0;
173
+ const allDurationOptions = [
174
+ { value: '60', minutes: 60, children: '1 hour' },
175
+ { value: '720', minutes: 720, children: '12 hours' },
176
+ { value: '1440', minutes: 1440, children: '1 day' },
177
+ { value: '2880', minutes: 2880, children: '2 days' },
178
+ { value: '4320', minutes: 4320, children: '3 days' },
179
+ { value: '5760', minutes: 5760, children: '4 days' },
180
+ { value: '7200', minutes: 7200, children: '5 days' },
181
+ { value: '8640', minutes: 8640, children: '6 days' },
182
+ { value: '10080', minutes: 10080, children: '7 days' },
183
+ { value: '20160', minutes: 20160, children: '14 days' },
184
+ { value: '43200', minutes: 43200, children: '30 days' },
185
+ ];
186
+ return [
187
+ { value: 'none', children: 'No protection' },
188
+ ...allDurationOptions
189
+ .filter((opt) => maxDurationMinutes === 0 || opt.minutes <= maxDurationMinutes)
190
+ .map(({ value, children }) => ({ value, children })),
191
+ ...(allowForever ? [{ value: '0', children: 'Forever' }] : []),
192
+ ];
193
+ })(), value: formik.values.protectionDurationMinutes, onChange: (e) => formik.setFieldValue('protectionDurationMinutes', e.target.value), disabled: isCreatingClone, style: { minWidth: 100 } }), _jsxs("p", { className: styles.remark, children: ["Select how long this clone should be protected from deletion. Protected clones cannot be deleted manually or automatically.", _jsx("br", {}), "Check disk space regularly and delete this clone once the work is done."] })] }), _jsxs("div", { className: cn(styles.marginBottom, styles.section), children: [_jsxs(Paper, { className: styles.summary, children: [_jsx(InfoIcon, { className: styles.summaryIcon }), _jsxs("div", { className: styles.params, children: [_jsxs("p", { className: styles.param, children: [_jsx("span", { children: "Data size:" }), _jsx("strong", { children: ((_j = stores.main.instance.state) === null || _j === void 0 ? void 0 : _j.dataSize)
143
194
  ? formatBytesIEC(stores.main.instance.state.dataSize)
144
- : '-' })] }), _jsxs("p", { className: styles.param, children: [_jsx("span", { children: "Expected cloning time:" }), _jsxs("strong", { children: [round((_k = stores.main.instance.state) === null || _k === void 0 ? void 0 : _k.cloning.expectedCloningTime, 2), ' ', "s"] })] })] })] }), stores.main.cloneError && (_jsx("div", { className: cn(styles.marginBottom, styles.section), children: _jsx(ErrorStub, { message: stores.main.cloneError }) })), _jsx("div", { className: styles.controls, children: _jsxs(Button, { onClick: formik.submitForm, variant: "primary", size: "medium", isDisabled: isCreatingClone, children: ["Create clone", isCreatingClone && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }) })] })] }), _jsx("div", { className: styles.form, children: _jsxs("div", { className: styles.snippetContainer, children: [_jsx(SectionTitle, { className: styles.title, tag: "h1", level: 1, text: "The same using CLI" }), _jsx("p", { className: styles.text, children: "Alternatively, you can create a new clone using CLI. Fill the form, copy the command below and paste it into your terminal." }), _jsx(SyntaxHighlight, { wrapLines: true, content: getCliCreateCloneCommand(formik.values) }), _jsx(SectionTitle, { className: styles.title, tag: "h2", level: 2, text: "Check clone status" }), _jsx("p", { className: styles.text, children: "To check the status of your newly created clone, use the command below." }), _jsx(SyntaxHighlight, { content: getCliCloneStatus(formik.values.cloneId) })] }) })] })] }));
195
+ : '-' })] }), _jsxs("p", { className: styles.param, children: [_jsx("span", { children: "Expected cloning time:" }), _jsxs("strong", { children: [round((_k = stores.main.instance.state) === null || _k === void 0 ? void 0 : _k.cloning.expectedCloningTime, 2), ' ', "s"] })] })] })] }), stores.main.cloneError && (_jsx("div", { className: cn(styles.marginBottom, styles.section), children: _jsx(ErrorStub, { message: stores.main.cloneError }) })), _jsx("div", { className: styles.controls, children: _jsxs(Button, { onClick: formik.submitForm, variant: "primary", size: "medium", isDisabled: isCreatingClone, children: ["Create clone", isCreatingClone && (_jsx(Spinner, { size: "sm", className: styles.spinner }))] }) })] })] }), _jsx("div", { className: styles.form, children: _jsxs("div", { className: styles.snippetContainer, children: [_jsx(SectionTitle, { className: styles.title, tag: "h1", level: 1, text: "The same using CLI" }), _jsx("p", { className: styles.text, children: "Alternatively, you can create a new clone using CLI. Fill the form, copy the command below and paste it into your terminal." }), _jsx(SyntaxHighlight, { wrapLines: true, content: getCliCreateCloneCommand(formik.values, showPassword) }), _jsx(SectionTitle, { className: styles.title, tag: "h2", level: 2, text: "Check clone status" }), _jsx("p", { className: styles.text, children: "To check the status of your newly created clone, use the command below." }), _jsx(SyntaxHighlight, { content: getCliCloneStatus(formik.values.cloneId) })] }) })] })] }));
145
196
  });
@@ -31,7 +31,7 @@ export declare class MainStore {
31
31
  load: (instanceId: string) => Promise<boolean | undefined>;
32
32
  createClone: (data: FormValues) => Promise<boolean>;
33
33
  getBranches: (instanceId: string) => Promise<import("@postgres.ai/shared/types/api/endpoints/getBranches").Branch[] | null | undefined>;
34
- getSnapshots: (instanceId: string, branchName?: string) => Promise<{
34
+ getSnapshots: (instanceId: string, branchName?: string, dataset?: string) => Promise<{
35
35
  createdAtDate: Date;
36
36
  dataStateAtDate: Date;
37
37
  numClones: string | number;
@@ -49,12 +49,13 @@ export class MainStore {
49
49
  this.getBranchesError = await error.json().then((err) => err);
50
50
  return response;
51
51
  };
52
- this.getSnapshots = async (instanceId, branchName) => {
52
+ this.getSnapshots = async (instanceId, branchName, dataset) => {
53
53
  if (!this.api.getSnapshots)
54
54
  return;
55
55
  const { response, error } = await this.api.getSnapshots({
56
56
  instanceId,
57
57
  branchName,
58
+ dataset,
58
59
  });
59
60
  if (error)
60
61
  this.getSnapshotsError = await error.json().then((err) => err);
@@ -111,4 +111,18 @@
111
111
  .pageTitle {
112
112
  margin-top: 8px;
113
113
  line-height: 26px;
114
+ }
115
+
116
+ .passwordActions {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
120
+ margin-top: 8px;
121
+ margin-bottom: 16px;
122
+ }
123
+
124
+ .passwordHint {
125
+ font-size: 12px;
126
+ color: #f57c00;
127
+ font-weight: 500;
114
128
  }
@@ -1,11 +1,12 @@
1
1
  /// <reference types="react" />
2
2
  export declare type FormValues = {
3
3
  branch: string;
4
+ dataset: string;
4
5
  cloneId: string;
5
6
  snapshotId: string;
6
7
  dbUser: string;
7
8
  dbPassword: string;
8
- isProtected: boolean;
9
+ protectionDurationMinutes: string;
9
10
  };
10
11
  export declare const useForm: (onSubmit: (values: FormValues) => void) => {
11
12
  initialValues: FormValues;
@@ -13,17 +13,18 @@ const Schema = Yup.object().shape({
13
13
  snapshotId: Yup.string().required('Date state time is required'),
14
14
  dbUser: Yup.string().required('Database username is required'),
15
15
  dbPassword: Yup.string().required('Database password is required'),
16
- isProtected: Yup.boolean(),
16
+ protectionDurationMinutes: Yup.string(),
17
17
  });
18
18
  export const useForm = (onSubmit) => {
19
19
  const formik = useFormik({
20
20
  initialValues: {
21
21
  branch: '',
22
+ dataset: '',
22
23
  cloneId: '',
23
24
  snapshotId: '',
24
25
  dbUser: '',
25
26
  dbPassword: '',
26
- isProtected: false,
27
+ protectionDurationMinutes: 'none',
27
28
  },
28
29
  validationSchema: Schema,
29
30
  onSubmit,
@@ -1,3 +1,3 @@
1
1
  import { FormValues } from '@postgres.ai/shared/pages/CreateClone/useForm';
2
- export declare const getCliCreateCloneCommand: (values: FormValues) => string;
2
+ export declare const getCliCreateCloneCommand: (values: FormValues, showPassword?: boolean) => string;
3
3
  export declare const getCliCloneStatus: (cloneId: string) => string;
@@ -1,17 +1,29 @@
1
- export const getCliCreateCloneCommand = (values) => {
2
- const { dbUser, dbPassword, branch, isProtected, cloneId } = values;
1
+ // escape string for use in single-quoted shell argument
2
+ const shellEscape = (str) => {
3
+ // replace single quotes with: end quote, escaped quote, start quote
4
+ return "'" + str.replace(/'/g, "'\\''") + "'";
5
+ };
6
+ export const getCliCreateCloneCommand = (values, showPassword) => {
7
+ const { dbUser, dbPassword, snapshotId, protectionDurationMinutes, cloneId } = values;
8
+ const usernameDisplay = dbUser ? shellEscape(dbUser) : `<USERNAME>`;
9
+ const passwordDisplay = dbPassword
10
+ ? (showPassword ? shellEscape(dbPassword) : dbPassword.replace(/./g, '*'))
11
+ : `<PASSWORD>`;
12
+ const cloneIdDisplay = cloneId ? shellEscape(cloneId) : `<CLONE_ID>`;
13
+ const protectedFlag = protectionDurationMinutes !== 'none' ? `--protected ${protectionDurationMinutes}` : '';
3
14
  return `dblab clone create \
4
15
 
5
- --username ${dbUser ? dbUser : `<USERNAME>`} \
16
+ --username ${usernameDisplay} \
6
17
 
7
- --password ${dbPassword ? dbPassword.replace(/./g, '*') : `<PASSWORD>`} \
18
+ --password ${passwordDisplay} \
8
19
 
9
- ${branch ? `--branch ${branch}` : ``} \
20
+ ${snapshotId ? `--snapshot-id ${shellEscape(snapshotId)}` : ``} \
10
21
 
11
- ${isProtected ? `--protected` : ''} \
22
+ ${protectedFlag} \
12
23
 
13
- --id ${cloneId ? cloneId : `<CLONE_ID>`} \ `;
24
+ --id ${cloneIdDisplay} \ `;
14
25
  };
15
26
  export const getCliCloneStatus = (cloneId) => {
16
- return `dblab clone status ${cloneId ? cloneId : `<CLONE_ID>`}`;
27
+ const cloneIdDisplay = cloneId ? shellEscape(cloneId) : `<CLONE_ID>`;
28
+ return `dblab clone status ${cloneIdDisplay}`;
17
29
  };
@@ -316,7 +316,9 @@ export const Configuration = observer(({ instanceId, switchActiveTab, reload, is
316
316
  dockerPath: initialRender
317
317
  ? formik.values.dockerPath
318
318
  : data.map((image) => image.location)[0],
319
- sharedPreloadLibraries: currentPreloadLibraries || '',
319
+ sharedPreloadLibraries: initialRender
320
+ ? formik.values.sharedPreloadLibraries
321
+ : currentPreloadLibraries || '',
320
322
  });
321
323
  }
322
324
  else {
@@ -1,8 +1,9 @@
1
- /// <reference types="react" />
1
+ import { CSSProperties } from 'react';
2
2
  declare type Props = {
3
3
  value: number;
4
4
  total: number;
5
5
  thresholdPercent: number;
6
+ style?: CSSProperties;
6
7
  };
7
8
  export declare const ProgressBar: (props: Props) => JSX.Element;
8
9
  export {};
@@ -39,7 +39,7 @@ const useStyles = makeStyles((theme) => ({
39
39
  }), { index: 1 });
40
40
  export const ProgressBar = (props) => {
41
41
  const classes = useStyles();
42
- return (_jsxs("div", { className: classes.root, children: [_jsx("div", { className: classes.indicator, style: { width: `${(props.value / props.total) * 100}%` } }), _jsx(Tooltip, { content: `+${props.thresholdPercent}% disk usage may result in performance degradation`, children: _jsx(PoinerIcon, { className: classes.pointer, style: {
42
+ return (_jsxs("div", { className: classes.root, style: props.style, children: [_jsx("div", { className: classes.indicator, style: { width: `${props.total === 0 ? 0 : (props.value / props.total) * 100}%` } }), _jsx(Tooltip, { content: `+${props.thresholdPercent}% disk usage may result in performance degradation`, children: _jsx(PoinerIcon, { className: classes.pointer, style: {
43
43
  left: `${props.thresholdPercent}%`,
44
44
  } }) })] }));
45
45
  };
@@ -1,15 +1,13 @@
1
1
  /// <reference types="react" />
2
- declare type Props = {
2
+ export declare type DatasetInfo = {
3
3
  id: string | null;
4
4
  name: string;
5
- totalDataSize: number;
5
+ showName: boolean;
6
+ status: 'refreshing' | 'active' | 'empty';
6
7
  mode: string;
7
- refreshingStartDate: Date | null;
8
+ usedDataSize: number;
8
9
  clonesCount: number;
9
10
  snapshotsCount: number;
10
- usedDataSize: number;
11
- freeDataSize: number;
12
- status: 'refreshing' | 'active' | 'empty';
11
+ refreshingStartDate: Date | null;
13
12
  };
14
- export declare const Disk: (props: Props) => JSX.Element;
15
- export {};
13
+ export declare const DatasetRow: (props: DatasetInfo) => JSX.Element;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles } from '@material-ui/core';
3
+ import { formatDistanceToNowStrict } from 'date-fns';
4
+ import { colors } from '@postgres.ai/shared/styles/colors';
5
+ import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
6
+ import { formatUTC, isValidDate } from '@postgres.ai/shared/utils/date';
7
+ import { Property } from '../../../components/Property';
8
+ import { ActionsMenu } from '../../Disk/ActionsMenu';
9
+ import { Status } from '../../Disk/Status';
10
+ const useStyles = makeStyles({
11
+ root: {
12
+ border: `1px solid ${colors.consoleStroke}`,
13
+ padding: '6px 8px 8px',
14
+ borderRadius: '4px',
15
+ backgroundColor: colors.white,
16
+ },
17
+ header: {
18
+ display: 'flex',
19
+ justifyContent: 'space-between',
20
+ alignItems: 'center',
21
+ },
22
+ titleWrapper: {
23
+ display: 'flex',
24
+ flex: '1 1 auto',
25
+ alignItems: 'center',
26
+ marginRight: '16px',
27
+ minWidth: 0,
28
+ },
29
+ title: {
30
+ fontWeight: 700,
31
+ fontSize: '14px',
32
+ margin: '0 4px 0 0',
33
+ whiteSpace: 'nowrap',
34
+ textOverflow: 'ellipsis',
35
+ overflow: 'hidden',
36
+ },
37
+ content: {
38
+ marginTop: '8px',
39
+ },
40
+ uppercaseContent: {
41
+ textTransform: 'uppercase',
42
+ },
43
+ }, { index: 1 });
44
+ export const DatasetRow = (props) => {
45
+ var _a, _b;
46
+ const classes = useStyles();
47
+ return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.header, children: [props.showName ? (_jsxs("div", { className: classes.titleWrapper, children: [_jsx("h6", { title: props.name, className: classes.title, children: props.name }), _jsx(ActionsMenu, { poolId: props.id, poolName: (_a = props.id) !== null && _a !== void 0 ? _a : props.name, isActive: props.status === 'active' })] })) : (_jsx(ActionsMenu, { poolId: props.id, poolName: (_b = props.id) !== null && _b !== void 0 ? _b : props.name, isActive: props.status === 'active' })), _jsx(Status, { value: props.status, hasWarning: false })] }), _jsx(Property, { name: "Mode", classes: { content: classes.uppercaseContent }, children: props.mode }), props.status === 'refreshing' && props.refreshingStartDate && (_jsx("div", { className: classes.content, children: _jsxs(Property, { name: "Refreshing started at", children: [formatUTC(props.refreshingStartDate, 'yyyy-MM-dd HH:mm:ss'), " UTC (", isValidDate(props.refreshingStartDate)
48
+ ? formatDistanceToNowStrict(props.refreshingStartDate, {
49
+ addSuffix: true,
50
+ })
51
+ : '-', ")"] }) })), _jsxs("div", { className: classes.content, children: [_jsx(Property, { name: "Clones", children: props.clonesCount }), _jsx(Property, { name: "Snapshots", children: props.snapshotsCount })] }), _jsx("div", { className: classes.content, children: _jsx(Property, { name: "Size", children: formatBytesIEC(props.usedDataSize) }) })] }));
52
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { DatasetInfo } from './DatasetRow';
3
+ declare type Props = {
4
+ poolName: string;
5
+ totalSize: number;
6
+ freeSize: number;
7
+ datasets: DatasetInfo[];
8
+ };
9
+ export declare const PoolSection: (props: Props) => JSX.Element;
10
+ export {};
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles } from '@material-ui/core';
3
+ import { colors } from '@postgres.ai/shared/styles/colors';
4
+ import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
5
+ import { Status as PerformanceStatus } from '@postgres.ai/shared/components/Status';
6
+ import { ProgressBar } from '../Disk/ProgressBar';
7
+ import { DatasetRow } from './DatasetRow';
8
+ const WARNING_THRESHOLD_PERCENT = 80;
9
+ const useStyles = makeStyles({
10
+ root: {
11
+ border: `1px solid ${colors.consoleStroke}`,
12
+ padding: '6px 8px 8px',
13
+ borderRadius: '4px',
14
+ backgroundColor: colors.consoleMenuBackground,
15
+ '& + $root': {
16
+ marginTop: '8px',
17
+ },
18
+ },
19
+ headerRow: {
20
+ display: 'flex',
21
+ justifyContent: 'space-between',
22
+ alignItems: 'baseline',
23
+ marginBottom: '4px',
24
+ },
25
+ poolName: {
26
+ fontWeight: 700,
27
+ fontSize: '14px',
28
+ },
29
+ poolSize: {
30
+ fontSize: '13px',
31
+ fontWeight: 700,
32
+ },
33
+ progressBarWrapper: {
34
+ marginTop: '4px',
35
+ },
36
+ poolStats: {
37
+ fontSize: '12px',
38
+ marginTop: '2px',
39
+ },
40
+ warningMessage: {
41
+ fontSize: '10px',
42
+ marginTop: '4px',
43
+ },
44
+ datasets: {
45
+ marginTop: '8px',
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ gap: '6px',
49
+ },
50
+ }, { index: 1 });
51
+ const getPercent = (value, total) => total === 0 ? 0 : Math.round((value / total) * 100);
52
+ export const PoolSection = (props) => {
53
+ const classes = useStyles();
54
+ const { poolName, totalSize, freeSize, datasets } = props;
55
+ const usedSize = Math.max(0, totalSize - freeSize);
56
+ const usedPercent = getPercent(usedSize, totalSize);
57
+ const freePercent = Math.min(100, getPercent(freeSize, totalSize));
58
+ const hasActiveDataset = datasets.some((d) => d.status === 'active');
59
+ const shouldShowWarning = hasActiveDataset && usedPercent > WARNING_THRESHOLD_PERCENT;
60
+ return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.headerRow, children: [_jsx("span", { className: classes.poolName, children: poolName }), _jsx("span", { className: classes.poolSize, children: formatBytesIEC(totalSize) })] }), _jsx("div", { className: classes.progressBarWrapper, children: _jsx(ProgressBar, { value: usedSize, total: totalSize, thresholdPercent: WARNING_THRESHOLD_PERCENT, style: { marginTop: 0 } }) }), _jsxs("div", { className: classes.poolStats, children: [_jsx("strong", { children: formatBytesIEC(usedSize) }), " used (", usedPercent, "%) \u00B7 ", freePercent, "% free"] }), shouldShowWarning && (_jsxs(PerformanceStatus, { type: "warning", className: classes.warningMessage, children: ["+", WARNING_THRESHOLD_PERCENT, "% disk usage may result in performance degradation"] })), _jsx("div", { className: classes.datasets, children: datasets.map((dataset) => {
61
+ var _a;
62
+ return (_jsx(DatasetRow, { ...dataset }, (_a = dataset.id) !== null && _a !== void 0 ? _a : dataset.name));
63
+ }) })] }));
64
+ };
@@ -8,17 +8,61 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
8
8
  import { observer } from 'mobx-react-lite';
9
9
  import { useStores } from '@postgres.ai/shared/pages/Instance/context';
10
10
  import { Section } from '../components/Section';
11
- import { Disk } from './Disk';
11
+ import { PoolSection } from './PoolSection';
12
12
  export const Disks = observer(() => {
13
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
13
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
14
14
  const stores = useStores();
15
15
  const { instance, snapshots } = stores.main;
16
16
  if (!instance)
17
17
  return null;
18
18
  if (!snapshots)
19
19
  return null;
20
- return (_jsx(Section, { title: "Disks", children: (_k = (_c = (_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.pools) === null || _b === void 0 ? void 0 : _b.map((pool) => {
21
- var _a, _b, _c, _d, _e;
22
- return (_jsx(Disk, { status: pool.status, name: pool.name, id: pool.name, mode: pool.mode, clonesCount: pool.cloneList.length, snapshotsCount: (_b = (_a = snapshots.data) === null || _a === void 0 ? void 0 : _a.filter((snapshot) => snapshot.pool === pool.name).length) !== null && _b !== void 0 ? _b : 0, totalDataSize: pool.fileSystem.size, usedDataSize: pool.fileSystem.used, freeDataSize: pool.fileSystem.free, refreshingStartDate: (_e = (_d = (_c = instance.state) === null || _c === void 0 ? void 0 : _c.retrieving) === null || _d === void 0 ? void 0 : _d.lastRefresh) !== null && _e !== void 0 ? _e : null }, pool.name));
23
- })) !== null && _c !== void 0 ? _c : (((_d = instance.state) === null || _d === void 0 ? void 0 : _d.fileSystem) && (_jsx(Disk, { status: 'active', name: 'Main', id: null, mode: "ZFS", clonesCount: (_g = (_f = (_e = instance.state) === null || _e === void 0 ? void 0 : _e.clones) === null || _f === void 0 ? void 0 : _f.length) !== null && _g !== void 0 ? _g : instance.state.cloning.clones.length, snapshotsCount: (_j = (_h = snapshots.data) === null || _h === void 0 ? void 0 : _h.length) !== null && _j !== void 0 ? _j : 0, totalDataSize: instance.state.fileSystem.size, usedDataSize: instance.state.fileSystem.used, freeDataSize: instance.state.fileSystem.free, refreshingStartDate: null })))) !== null && _k !== void 0 ? _k : (_jsxs(_Fragment, { children: ["Disk information is ", _jsx("strong", { children: "unavailable" }), "."] })) }));
20
+ const pools = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.pools;
21
+ if (pools && pools.length > 0) {
22
+ const poolGroups = {};
23
+ for (const pool of pools) {
24
+ const slashIdx = pool.name.indexOf('/');
25
+ const poolName = slashIdx !== -1 ? pool.name.slice(0, slashIdx) : pool.name;
26
+ if (!poolGroups[poolName])
27
+ poolGroups[poolName] = [];
28
+ poolGroups[poolName].push(pool);
29
+ }
30
+ return (_jsx(Section, { title: "Disks", children: Object.entries(poolGroups).map(([poolName, poolList]) => {
31
+ const firstPool = poolList[0];
32
+ const datasets = poolList.map((pool) => {
33
+ var _a, _b, _c, _d, _e;
34
+ const slashIdx = pool.name.indexOf('/');
35
+ const datasetName = slashIdx !== -1 ? pool.name.slice(slashIdx + 1) : pool.name;
36
+ const datasetSnapshots = (_b = (_a = snapshots.data) === null || _a === void 0 ? void 0 : _a.filter((s) => s.pool === pool.name)) !== null && _b !== void 0 ? _b : [];
37
+ return {
38
+ id: pool.name,
39
+ name: datasetName,
40
+ showName: slashIdx !== -1,
41
+ status: pool.status,
42
+ mode: pool.mode,
43
+ usedDataSize: pool.fileSystem.used,
44
+ clonesCount: pool.cloneList.length,
45
+ snapshotsCount: datasetSnapshots.length,
46
+ refreshingStartDate: (_e = (_d = (_c = instance.state) === null || _c === void 0 ? void 0 : _c.retrieving) === null || _d === void 0 ? void 0 : _d.lastRefresh) !== null && _e !== void 0 ? _e : null,
47
+ };
48
+ });
49
+ return (_jsx(PoolSection, { poolName: poolName, totalSize: firstPool.fileSystem.size, freeSize: firstPool.fileSystem.free, datasets: datasets }, poolName));
50
+ }) }));
51
+ }
52
+ if ((_b = instance.state) === null || _b === void 0 ? void 0 : _b.fileSystem) {
53
+ const allSnapshots = (_c = snapshots.data) !== null && _c !== void 0 ? _c : [];
54
+ const dataset = {
55
+ id: null,
56
+ name: 'Main',
57
+ showName: false,
58
+ status: 'active',
59
+ mode: 'zfs',
60
+ usedDataSize: instance.state.fileSystem.used,
61
+ clonesCount: (_f = (_e = (_d = instance.state) === null || _d === void 0 ? void 0 : _d.clones) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : instance.state.cloning.clones.length,
62
+ snapshotsCount: allSnapshots.length,
63
+ refreshingStartDate: (_j = (_h = (_g = instance.state) === null || _g === void 0 ? void 0 : _g.retrieving) === null || _h === void 0 ? void 0 : _h.lastRefresh) !== null && _j !== void 0 ? _j : null,
64
+ };
65
+ return (_jsx(Section, { title: "Disks", children: _jsx(PoolSection, { poolName: "Main", totalSize: instance.state.fileSystem.size, freeSize: instance.state.fileSystem.free, datasets: [dataset] }) }));
66
+ }
67
+ return (_jsx(Section, { title: "Disks", children: _jsxs(_Fragment, { children: ["Disk information is ", _jsx("strong", { children: "unavailable" }), "."] }) }));
24
68
  });
@@ -113,7 +113,7 @@ const SnapshotListItem = ({ snapshot, setSnapshotModal, openClonesModal, }) => {
113
113
  const timeAgo = formatDistanceSafe(snapshot.createdAtDate);
114
114
  const history = useHistory();
115
115
  const host = useHost();
116
- return (_jsx("div", { className: classes.commitItem, children: _jsxs("div", { className: classes.gridContainer, children: [_jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: snapshot.message || '-' }), _jsxs("div", { className: classes.infoContent, title: snapshot.dataStateAt, children: [timeAgo, " ago"] })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Pool" }), _jsx("div", { className: classes.infoContent, children: (_a = snapshot.pool) !== null && _a !== void 0 ? _a : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Number of clones" }), _jsx("div", { className: classes.infoContent, children: (_b = snapshot.numClones) !== null && _b !== void 0 ? _b : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Logical Size" }), _jsx("div", { className: classes.infoContent, children: snapshot.logicalSize ? formatBytesIEC(snapshot.logicalSize) : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Physical Size" }), _jsx("div", { className: classes.infoContent, children: snapshot.physicalSize
116
+ return (_jsx("div", { className: classes.commitItem, children: _jsxs("div", { className: classes.gridContainer, children: [_jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: snapshot.message || '-' }), _jsx("div", { className: classes.infoContent, title: snapshot.dataStateAt, children: timeAgo })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Pool" }), _jsx("div", { className: classes.infoContent, children: (_a = snapshot.pool) !== null && _a !== void 0 ? _a : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Number of clones" }), _jsx("div", { className: classes.infoContent, children: (_b = snapshot.numClones) !== null && _b !== void 0 ? _b : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Logical Size" }), _jsx("div", { className: classes.infoContent, children: snapshot.logicalSize ? formatBytesIEC(snapshot.logicalSize) : '-' })] }), _jsxs("div", { className: classes.infoBlock, children: [_jsx("div", { className: classes.header, children: "Physical Size" }), _jsx("div", { className: classes.infoContent, children: snapshot.physicalSize
117
117
  ? formatBytesIEC(snapshot.physicalSize)
118
118
  : '-' })] }), _jsxs("div", { className: classes.actionsContainer, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { className: classes.snapshotId, children: snapshot.id }), _jsx("div", { className: classes.copyButtonContainer, title: "Copy snapshot ID", children: _jsx(IconButton, { className: classes.copyButton, onClick: (e) => {
119
119
  e.stopPropagation();
@@ -5,7 +5,7 @@ export declare type CreateClone = (args: {
5
5
  snapshotId: string;
6
6
  dbUser: string;
7
7
  dbPassword: string;
8
- isProtected: boolean;
8
+ protectionDurationMinutes: string;
9
9
  branch?: string;
10
10
  }) => Promise<{
11
11
  response: Clone | null;
@@ -3,6 +3,7 @@ export declare type UpdateClone = (args: {
3
3
  cloneId: string;
4
4
  clone: {
5
5
  isProtected: boolean;
6
+ protectionDurationMinutes?: number;
6
7
  };
7
8
  }) => Promise<{
8
9
  response: true | null;
@@ -8,9 +8,12 @@ export declare type CloneDto = {
8
8
  message: string;
9
9
  };
10
10
  protected: boolean;
11
+ protectedTill?: string;
11
12
  metadata: {
12
13
  cloneDiffSize: number;
13
14
  cloningTime: number;
15
+ protectionLeaseDurationMinutes?: number;
16
+ protectionMaxDurationMinutes?: number;
14
17
  };
15
18
  db: {
16
19
  username: string;
@@ -22,6 +25,7 @@ export declare type CloneDto = {
22
25
  export declare const formatCloneDto: (dto: CloneDto) => {
23
26
  createdAt: string;
24
27
  createdAtDate: Date;
28
+ protectedTillDate: Date | null;
25
29
  snapshot: {
26
30
  createdAtDate: Date;
27
31
  dataStateAtDate: Date;
@@ -43,9 +47,12 @@ export declare const formatCloneDto: (dto: CloneDto) => {
43
47
  message: string;
44
48
  };
45
49
  protected: boolean;
50
+ protectedTill?: string | undefined;
46
51
  metadata: {
47
52
  cloneDiffSize: number;
48
53
  cloningTime: number;
54
+ protectionLeaseDurationMinutes?: number;
55
+ protectionMaxDurationMinutes?: number;
49
56
  };
50
57
  db: {
51
58
  username: string;
@@ -10,5 +10,6 @@ export const formatCloneDto = (dto) => ({
10
10
  ...dto,
11
11
  createdAt: dto.createdAt,
12
12
  createdAtDate: parseDate(dto.createdAt),
13
+ protectedTillDate: dto.protectedTill ? parseDate(dto.protectedTill) : null,
13
14
  snapshot: dto.snapshot ? formatSnapshotDto(dto.snapshot) : null,
14
15
  });
@@ -61,6 +61,7 @@ export declare const formatInstanceDto: (dto: InstanceDto) => {
61
61
  clones: {
62
62
  createdAt: string;
63
63
  createdAtDate: Date;
64
+ protectedTillDate: Date | null;
64
65
  snapshot: {
65
66
  createdAtDate: Date;
66
67
  dataStateAtDate: Date;
@@ -82,9 +83,12 @@ export declare const formatInstanceDto: (dto: InstanceDto) => {
82
83
  message: string;
83
84
  };
84
85
  protected: boolean;
86
+ protectedTill?: string | undefined;
85
87
  metadata: {
86
88
  cloneDiffSize: number;
87
89
  cloningTime: number;
90
+ protectionLeaseDurationMinutes?: number | undefined;
91
+ protectionMaxDurationMinutes?: number | undefined;
88
92
  };
89
93
  db: {
90
94
  username: string;
@@ -93,6 +97,8 @@ export declare const formatInstanceDto: (dto: InstanceDto) => {
93
97
  };
94
98
  }[];
95
99
  expectedCloningTime: number;
100
+ protectionLeaseDurationMinutes: number | undefined;
101
+ protectionMaxDurationMinutes: number | undefined;
96
102
  };
97
103
  pools: import("./pool").PoolDto[] | null;
98
104
  dataSize: number | null;
@@ -5,6 +5,8 @@ export declare type InstanceStateDto = {
5
5
  clones: CloneDto[];
6
6
  expectedCloningTime: number;
7
7
  numClones?: number;
8
+ protectionLeaseDurationMinutes?: number;
9
+ protectionMaxDurationMinutes?: number;
8
10
  };
9
11
  status: {
10
12
  code: 'OK' | 'WARNING' | 'NO_RESPONSE';
@@ -64,6 +66,7 @@ export declare const formatInstanceStateDto: (dto: InstanceStateDto) => {
64
66
  clones: {
65
67
  createdAt: string;
66
68
  createdAtDate: Date;
69
+ protectedTillDate: Date | null;
67
70
  snapshot: {
68
71
  createdAtDate: Date;
69
72
  dataStateAtDate: Date;
@@ -85,9 +88,12 @@ export declare const formatInstanceStateDto: (dto: InstanceStateDto) => {
85
88
  message: string;
86
89
  };
87
90
  protected: boolean;
91
+ protectedTill?: string | undefined;
88
92
  metadata: {
89
93
  cloneDiffSize: number;
90
94
  cloningTime: number;
95
+ protectionLeaseDurationMinutes?: number | undefined;
96
+ protectionMaxDurationMinutes?: number | undefined;
91
97
  };
92
98
  db: {
93
99
  username: string;
@@ -96,6 +102,8 @@ export declare const formatInstanceStateDto: (dto: InstanceStateDto) => {
96
102
  };
97
103
  }[];
98
104
  expectedCloningTime: number;
105
+ protectionLeaseDurationMinutes: number | undefined;
106
+ protectionMaxDurationMinutes: number | undefined;
99
107
  };
100
108
  pools: PoolDto[] | null;
101
109
  dataSize: number | null;
@@ -1,7 +1,7 @@
1
1
  import { formatCloneDto, } from '@postgres.ai/shared/types/api/entities/clone';
2
2
  import { formatPoolDto, } from '@postgres.ai/shared/types/api/entities/pool';
3
3
  export const formatInstanceStateDto = (dto) => {
4
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
4
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
5
5
  if (!dto)
6
6
  return null;
7
7
  const pools = (_b = (_a = dto.pools) === null || _a === void 0 ? void 0 : _a.map(formatPoolDto)) !== null && _b !== void 0 ? _b : null;
@@ -35,8 +35,10 @@ export const formatInstanceStateDto = (dto) => {
35
35
  cloning: {
36
36
  clones: clones,
37
37
  expectedCloningTime: expectedCloningTime,
38
+ protectionLeaseDurationMinutes: (_o = dto.cloning) === null || _o === void 0 ? void 0 : _o.protectionLeaseDurationMinutes,
39
+ protectionMaxDurationMinutes: (_p = dto.cloning) === null || _p === void 0 ? void 0 : _p.protectionMaxDurationMinutes,
38
40
  },
39
41
  pools,
40
- dataSize: (_p = (_o = dto.dataSize) !== null && _o !== void 0 ? _o : pools === null || pools === void 0 ? void 0 : pools.reduce((sum, pool) => pool.fileSystem.dataSize + sum, 0)) !== null && _p !== void 0 ? _p : null,
42
+ dataSize: (_r = (_q = dto.dataSize) !== null && _q !== void 0 ? _q : pools === null || pools === void 0 ? void 0 : pools.reduce((sum, pool) => pool.fileSystem.dataSize + sum, 0)) !== null && _r !== void 0 ? _r : null,
41
43
  };
42
44
  };
@@ -1,6 +0,0 @@
1
- /// <reference types="react" />
2
- declare type Props = {
3
- className: string;
4
- };
5
- export declare const Marker: (props: Props) => JSX.Element;
6
- export {};
@@ -1,15 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { makeStyles } from '@material-ui/core';
3
- import clsx from 'clsx';
4
- import { CircleIcon } from '@postgres.ai/shared/icons/Circle';
5
- const useStyles = makeStyles({
6
- root: {
7
- display: 'inline',
8
- verticalAlign: 'middle',
9
- width: '10px',
10
- },
11
- }, { index: 1 });
12
- export const Marker = (props) => {
13
- const classes = useStyles();
14
- return _jsx(CircleIcon, { className: clsx(classes.root, props.className) });
15
- };
@@ -1,71 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { makeStyles } from '@material-ui/core';
3
- import { formatDistanceToNowStrict } from 'date-fns';
4
- import { colors } from '@postgres.ai/shared/styles/colors';
5
- import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
6
- import { Status as PerformanceStatus } from '@postgres.ai/shared/components/Status';
7
- import { formatUTC, isValidDate } from '@postgres.ai/shared/utils/date';
8
- import { Property } from '../../components/Property';
9
- import { ActionsMenu } from './ActionsMenu';
10
- import { Status } from './Status';
11
- import { Marker } from './Marker';
12
- import { ProgressBar } from './ProgressBar';
13
- const WARNING_THRESHOLD_PERCENT = 80;
14
- const useStyles = makeStyles({
15
- root: {
16
- border: `1px solid ${colors.consoleStroke}`,
17
- padding: '6px 8px 8px',
18
- borderRadius: '4px',
19
- '& + $root': {
20
- marginTop: '8px',
21
- },
22
- },
23
- header: {
24
- display: 'flex',
25
- justifyContent: 'space-between',
26
- alignItems: 'center',
27
- },
28
- titleWrapper: {
29
- display: 'flex',
30
- flex: '1 1 auto',
31
- alignItems: 'center',
32
- marginRight: '16px',
33
- minWidth: 0,
34
- },
35
- title: {
36
- fontWeight: 700,
37
- fontSize: '14px',
38
- margin: '0 4px 0 0',
39
- whiteSpace: 'nowrap',
40
- textOverflow: 'ellipsis',
41
- overflow: 'hidden',
42
- },
43
- content: {
44
- marginTop: '8px',
45
- },
46
- markerUsed: {
47
- color: colors.primary.light,
48
- },
49
- markerFree: {
50
- color: colors.gray,
51
- },
52
- warningMessage: {
53
- fontSize: '10px',
54
- marginTop: '6px',
55
- },
56
- uppercaseContent: {
57
- textTransform: 'uppercase',
58
- },
59
- }, { index: 1 });
60
- const getPercent = (value, total) => Math.round((value / total) * 100);
61
- export const Disk = (props) => {
62
- const classes = useStyles();
63
- const shouldShowWarning = props.status === 'active' &&
64
- getPercent(props.usedDataSize, props.totalDataSize) >
65
- WARNING_THRESHOLD_PERCENT;
66
- return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.header, children: [_jsxs("div", { className: classes.titleWrapper, children: [_jsx("h6", { title: props.name, className: classes.title, children: props.name }), _jsx(ActionsMenu, { poolId: props.id, poolName: props.name, isActive: props.status === 'active' })] }), _jsx(Status, { value: props.status, hasWarning: shouldShowWarning })] }), _jsx(Property, { name: "Mode", classes: { content: classes.uppercaseContent }, children: props.mode }), props.status === 'refreshing' && props.refreshingStartDate && (_jsx("div", { className: classes.content, children: _jsxs(Property, { name: "Refreshing started at", children: [formatUTC(props.refreshingStartDate, 'yyyy-MM-dd HH:mm:ss'), " UTC (", isValidDate(props.refreshingStartDate)
67
- ? formatDistanceToNowStrict(props.refreshingStartDate, {
68
- addSuffix: true,
69
- })
70
- : '-', ")"] }) })), _jsxs("div", { className: classes.content, children: [_jsx(Property, { name: "Clones", children: props.clonesCount }), _jsx(Property, { name: "Snapshots", children: props.snapshotsCount })] }), _jsxs("div", { className: classes.content, children: [_jsx(Property, { name: "Size", children: formatBytesIEC(props.totalDataSize) }), _jsxs(Property, { name: _jsxs(_Fragment, { children: ["Used\u00A0", _jsx(Marker, { className: classes.markerUsed })] }), children: [formatBytesIEC(props.usedDataSize), " /", ' ', getPercent(props.usedDataSize, props.totalDataSize), " %"] }), _jsxs(Property, { name: _jsxs(_Fragment, { children: ["Free\u00A0", _jsx(Marker, { className: classes.markerFree })] }), children: [formatBytesIEC(props.freeDataSize), " /", ' ', getPercent(props.freeDataSize, props.totalDataSize), " %"] })] }), _jsx(ProgressBar, { value: props.usedDataSize, total: props.totalDataSize, thresholdPercent: WARNING_THRESHOLD_PERCENT }), shouldShowWarning && (_jsxs(PerformanceStatus, { type: "warning", className: classes.warningMessage, children: ["+", WARNING_THRESHOLD_PERCENT, "% disk usage may result in performance degradation"] }))] }));
71
- };