@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.
- package/components/Select/index.d.ts +1 -0
- package/components/TextField/index.d.ts +2 -1
- package/components/TextField/index.js +1 -1
- package/helpers/getEntropy.d.ts +8 -0
- package/helpers/getEntropy.js +39 -0
- package/icons/PostgresSQL/index.js +1 -39
- package/package.json +2 -2
- package/pages/Clone/index.js +49 -6
- package/pages/Clone/stores/Main.d.ts +1 -1
- package/pages/Clone/stores/Main.js +8 -2
- package/pages/CreateClone/index.js +75 -24
- package/pages/CreateClone/stores/Main.d.ts +1 -1
- package/pages/CreateClone/stores/Main.js +2 -1
- package/pages/CreateClone/styles.module.scss +14 -0
- package/pages/CreateClone/useForm.d.ts +2 -1
- package/pages/CreateClone/useForm.js +3 -2
- package/pages/CreateClone/utils/index.d.ts +1 -1
- package/pages/CreateClone/utils/index.js +20 -8
- package/pages/Instance/Configuration/index.js +3 -1
- package/pages/Instance/Info/Disks/Disk/ProgressBar/index.d.ts +2 -1
- package/pages/Instance/Info/Disks/Disk/ProgressBar/index.js +1 -1
- package/pages/Instance/Info/Disks/{Disk → PoolSection/DatasetRow}/index.d.ts +6 -8
- package/pages/Instance/Info/Disks/PoolSection/DatasetRow/index.js +52 -0
- package/pages/Instance/Info/Disks/PoolSection/index.d.ts +10 -0
- package/pages/Instance/Info/Disks/PoolSection/index.js +64 -0
- package/pages/Instance/Info/Disks/index.js +50 -6
- package/pages/Instance/Snapshots/components/SnapshotsList/index.js +1 -1
- package/types/api/endpoints/createClone.d.ts +1 -1
- package/types/api/endpoints/updateClone.d.ts +1 -0
- package/types/api/entities/clone.d.ts +7 -0
- package/types/api/entities/clone.js +1 -0
- package/types/api/entities/instance.d.ts +6 -0
- package/types/api/entities/instanceState.d.ts +8 -0
- package/types/api/entities/instanceState.js +4 -2
- package/pages/Instance/Info/Disks/Disk/Marker/index.d.ts +0 -6
- package/pages/Instance/Info/Disks/Disk/Marker/index.js +0 -15
- package/pages/Instance/Info/Disks/Disk/index.js +0 -71
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
};
|
package/helpers/getEntropy.d.ts
CHANGED
|
@@ -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;
|
package/helpers/getEntropy.js
CHANGED
|
@@ -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",
|
|
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-
|
|
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": "^
|
|
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",
|
package/pages/Clone/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
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: (
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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,
|
|
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
|
|
57
|
-
|
|
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(
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
74
|
-
formik.setFieldValue('branch', initiallySelectedBranch);
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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:
|
|
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:
|
|
126
|
-
children:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 ${
|
|
16
|
+
--username ${usernameDisplay} \
|
|
6
17
|
|
|
7
|
-
--password ${
|
|
18
|
+
--password ${passwordDisplay} \
|
|
8
19
|
|
|
9
|
-
${
|
|
20
|
+
${snapshotId ? `--snapshot-id ${shellEscape(snapshotId)}` : ``} \
|
|
10
21
|
|
|
11
|
-
${
|
|
22
|
+
${protectedFlag} \
|
|
12
23
|
|
|
13
|
-
--id ${
|
|
24
|
+
--id ${cloneIdDisplay} \ `;
|
|
14
25
|
};
|
|
15
26
|
export const getCliCloneStatus = (cloneId) => {
|
|
16
|
-
|
|
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:
|
|
319
|
+
sharedPreloadLibraries: initialRender
|
|
320
|
+
? formik.values.sharedPreloadLibraries
|
|
321
|
+
: currentPreloadLibraries || '',
|
|
320
322
|
});
|
|
321
323
|
}
|
|
322
324
|
else {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
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
|
|
2
|
+
export declare type DatasetInfo = {
|
|
3
3
|
id: string | null;
|
|
4
4
|
name: string;
|
|
5
|
-
|
|
5
|
+
showName: boolean;
|
|
6
|
+
status: 'refreshing' | 'active' | 'empty';
|
|
6
7
|
mode: string;
|
|
7
|
-
|
|
8
|
+
usedDataSize: number;
|
|
8
9
|
clonesCount: number;
|
|
9
10
|
snapshotsCount: number;
|
|
10
|
-
|
|
11
|
-
freeDataSize: number;
|
|
12
|
-
status: 'refreshing' | 'active' | 'empty';
|
|
11
|
+
refreshingStartDate: Date | null;
|
|
13
12
|
};
|
|
14
|
-
export declare const
|
|
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 {
|
|
11
|
+
import { PoolSection } from './PoolSection';
|
|
12
12
|
export const Disks = observer(() => {
|
|
13
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 || '-' }),
|
|
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();
|
|
@@ -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: (
|
|
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,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
|
-
};
|