@postgres.ai/shared 4.1.3 → 4.2.0

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 (137) hide show
  1. package/.gitlab-ci.yml +7 -3
  2. package/components/UpgradeCloneModal/index.tsx +145 -0
  3. package/dist/components/UpgradeCloneModal/index.d.ts +11 -0
  4. package/dist/components/UpgradeCloneModal/index.js +75 -0
  5. package/dist/package.json +2 -2
  6. package/dist/pages/Branches/Branch/index.js +27 -2
  7. package/dist/pages/Branches/Branch/stores/Main.d.ts +5 -0
  8. package/dist/pages/Branches/Branch/stores/Main.js +29 -0
  9. package/dist/pages/Branches/components/BranchesTable/index.js +2 -2
  10. package/dist/pages/Branches/index.js +10 -1
  11. package/dist/pages/Clone/Status/index.js +3 -0
  12. package/dist/pages/Clone/index.js +12 -4
  13. package/dist/pages/Clone/stores/Main.d.ts +9 -1
  14. package/dist/pages/Clone/stores/Main.js +40 -3
  15. package/dist/pages/CreateBranch/stores/Main.d.ts +3 -0
  16. package/dist/pages/CreateClone/index.js +2 -0
  17. package/dist/pages/CreateClone/stores/Main.d.ts +5 -1
  18. package/dist/pages/CreateClone/stores/Main.js +8 -3
  19. package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
  20. package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
  21. package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
  22. package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
  23. package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
  24. package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
  25. package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
  26. package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
  27. package/dist/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
  28. package/dist/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
  29. package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +13 -0
  30. package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
  31. package/dist/pages/Instance/Configuration/SimpleMode/index.d.ts +19 -0
  32. package/dist/pages/Instance/Configuration/SimpleMode/index.js +166 -0
  33. package/dist/pages/Instance/Configuration/configMode.d.ts +2 -0
  34. package/dist/pages/Instance/Configuration/configMode.js +7 -0
  35. package/dist/pages/Instance/Configuration/configOptions.d.ts +16 -0
  36. package/dist/pages/Instance/Configuration/configOptions.js +71 -0
  37. package/dist/pages/Instance/Configuration/connectionString.d.ts +20 -0
  38. package/dist/pages/Instance/Configuration/connectionString.js +129 -0
  39. package/dist/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
  40. package/dist/pages/Instance/Configuration/dockerCatalog.js +19 -0
  41. package/dist/pages/Instance/Configuration/index.d.ts +1 -2
  42. package/dist/pages/Instance/Configuration/index.js +115 -86
  43. package/dist/pages/Instance/Configuration/useForm.d.ts +20 -0
  44. package/dist/pages/Instance/Configuration/useForm.js +126 -5
  45. package/dist/pages/Instance/Configuration/utils/index.js +1 -17
  46. package/dist/pages/Instance/Info/Snapshots/Calendar/Day/index.js +3 -3
  47. package/dist/pages/Instance/Info/Snapshots/Calendar/utils.d.ts +3 -0
  48. package/dist/pages/Instance/Info/Snapshots/utils.d.ts +6 -0
  49. package/dist/pages/Instance/Snapshots/components/SnapshotsList/index.js +11 -1
  50. package/dist/pages/Instance/Snapshots/index.js +11 -3
  51. package/dist/pages/Instance/Snapshots/utils/index.d.ts +3 -0
  52. package/dist/pages/Instance/Tabs/styles.js +6 -6
  53. package/dist/pages/Instance/index.js +1 -3
  54. package/dist/pages/Instance/stores/Main.d.ts +22 -2
  55. package/dist/pages/Instance/stores/Main.js +26 -9
  56. package/dist/pages/Snapshots/Snapshot/index.js +28 -2
  57. package/dist/pages/Snapshots/Snapshot/stores/Main.d.ts +5 -0
  58. package/dist/pages/Snapshots/Snapshot/stores/Main.js +29 -0
  59. package/dist/stores/Snapshots.d.ts +1 -1
  60. package/dist/stores/Snapshots.js +12 -6
  61. package/dist/types/api/endpoints/getBranches.d.ts +6 -0
  62. package/dist/types/api/endpoints/probeSource.d.ts +32 -0
  63. package/dist/types/api/endpoints/probeSource.js +1 -0
  64. package/dist/types/api/endpoints/testDbSource.d.ts +3 -1
  65. package/dist/types/api/endpoints/testDbSource.js +6 -0
  66. package/dist/types/api/endpoints/updateBranch.d.ts +11 -0
  67. package/dist/types/api/endpoints/updateBranch.js +1 -0
  68. package/dist/types/api/endpoints/updateSnapshot.d.ts +11 -0
  69. package/dist/types/api/endpoints/updateSnapshot.js +1 -0
  70. package/dist/types/api/endpoints/upgradeClone.d.ts +8 -0
  71. package/dist/types/api/endpoints/upgradeClone.js +1 -0
  72. package/dist/types/api/entities/branchSnapshots.d.ts +3 -0
  73. package/dist/types/api/entities/clone.d.ts +9 -2
  74. package/dist/types/api/entities/config.d.ts +32 -0
  75. package/dist/types/api/entities/config.js +45 -18
  76. package/dist/types/api/entities/instance.d.ts +11 -1
  77. package/dist/types/api/entities/instanceState.d.ts +16 -1
  78. package/dist/types/api/entities/snapshot.d.ts +6 -0
  79. package/dist/utils/api.d.ts +1 -1
  80. package/dist/utils/api.js +6 -0
  81. package/dist/utils/clone.d.ts +3 -1
  82. package/dist/utils/clone.js +7 -1
  83. package/dist/utils/clonePoller.d.ts +12 -0
  84. package/dist/utils/clonePoller.js +41 -0
  85. package/dist/utils/date.js +12 -12
  86. package/dist/utils/protection.d.ts +5 -0
  87. package/dist/utils/protection.js +23 -0
  88. package/package.json +2 -2
  89. package/pages/Branches/Branch/index.tsx +70 -0
  90. package/pages/Branches/Branch/stores/Main.ts +40 -0
  91. package/pages/Branches/components/BranchesTable/index.tsx +2 -0
  92. package/pages/Branches/index.tsx +13 -1
  93. package/pages/Clone/Status/index.tsx +3 -0
  94. package/pages/Clone/index.tsx +58 -0
  95. package/pages/Clone/stores/Main.ts +51 -8
  96. package/pages/CreateClone/index.tsx +3 -0
  97. package/pages/CreateClone/stores/Main.ts +10 -8
  98. package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.tsx +129 -0
  99. package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.tsx +89 -0
  100. package/pages/Instance/Configuration/PhysicalMode/Sync/index.tsx +64 -0
  101. package/pages/Instance/Configuration/PhysicalMode/Walg/index.tsx +63 -0
  102. package/pages/Instance/Configuration/PhysicalMode/index.tsx +93 -0
  103. package/pages/Instance/Configuration/SimpleMode/PreviewCard.tsx +176 -0
  104. package/pages/Instance/Configuration/SimpleMode/index.tsx +303 -0
  105. package/pages/Instance/Configuration/configMode.ts +12 -0
  106. package/pages/Instance/Configuration/configOptions.ts +111 -0
  107. package/pages/Instance/Configuration/connectionString.ts +157 -0
  108. package/pages/Instance/Configuration/dockerCatalog.ts +21 -0
  109. package/pages/Instance/Configuration/index.tsx +151 -49
  110. package/pages/Instance/Configuration/useForm.ts +155 -5
  111. package/pages/Instance/Configuration/utils/index.ts +1 -17
  112. package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +3 -3
  113. package/pages/Instance/Snapshots/components/SnapshotsList/index.tsx +21 -1
  114. package/pages/Instance/Snapshots/index.tsx +15 -5
  115. package/pages/Instance/Tabs/styles.ts +7 -7
  116. package/pages/Instance/index.tsx +0 -4
  117. package/pages/Instance/stores/Main.ts +29 -7
  118. package/pages/Snapshots/Snapshot/index.tsx +71 -0
  119. package/pages/Snapshots/Snapshot/stores/Main.ts +40 -0
  120. package/postgres.ai-shared-4.2.0.tgz +0 -0
  121. package/stores/Snapshots.ts +16 -7
  122. package/types/api/endpoints/getBranches.ts +3 -0
  123. package/types/api/endpoints/probeSource.ts +34 -0
  124. package/types/api/endpoints/testDbSource.ts +10 -1
  125. package/types/api/endpoints/updateBranch.ts +11 -0
  126. package/types/api/endpoints/updateSnapshot.ts +11 -0
  127. package/types/api/endpoints/upgradeClone.ts +8 -0
  128. package/types/api/entities/clone.ts +13 -1
  129. package/types/api/entities/config.ts +46 -0
  130. package/types/api/entities/instanceState.ts +6 -0
  131. package/types/api/entities/snapshot.ts +3 -0
  132. package/utils/api.ts +8 -0
  133. package/utils/clone.ts +7 -1
  134. package/utils/clonePoller.ts +47 -0
  135. package/utils/date.ts +19 -14
  136. package/utils/protection.ts +25 -0
  137. package/postgres.ai-shared-4.1.3.tgz +0 -0
@@ -4,14 +4,64 @@
4
4
  * Unauthorized copying of this file, via any medium is strictly prohibited
5
5
  *--------------------------------------------------------------------------
6
6
  */
7
+ import { useMemo, useState } from 'react';
7
8
  import { useFormik } from 'formik';
8
9
  import * as Yup from 'yup';
10
+ import { ConnectionStringError, connectionStringFromFields, connectionStringToFields, } from './connectionString';
11
+ // Logical-mode required fields. Validation skips the source-connection block
12
+ // when retrievalMode is "physical" — that branch uses tool-specific inputs
13
+ // instead of a connection URL.
9
14
  const Schema = Yup.object().shape({
10
15
  dockerImage: Yup.string().required('Docker image is required'),
11
- dbname: Yup.string().required('Dbname is required'),
12
- host: Yup.string().required('Host is required'),
13
- port: Yup.string().required('Port is required'),
14
- username: Yup.string().required('Username is required'),
16
+ dbname: Yup.string().when('retrievalMode', {
17
+ is: 'logical',
18
+ then: (s) => s.required('Dbname is required'),
19
+ }),
20
+ host: Yup.string().when('retrievalMode', {
21
+ is: 'logical',
22
+ then: (s) => s.required('Host is required'),
23
+ }),
24
+ port: Yup.string().when('retrievalMode', {
25
+ is: 'logical',
26
+ then: (s) => s.required('Port is required'),
27
+ }),
28
+ username: Yup.string().when('retrievalMode', {
29
+ is: 'logical',
30
+ then: (s) => s.required('Username is required'),
31
+ }),
32
+ physicalEnvs: Yup.array()
33
+ .of(Yup.object().shape({
34
+ key: Yup.string(),
35
+ value: Yup.string(),
36
+ }))
37
+ .test('unique-keys', '', function (envs) {
38
+ if (!envs)
39
+ return true;
40
+ const positions = new Map();
41
+ envs.forEach((row, i) => {
42
+ var _a, _b;
43
+ const trimmed = ((_a = row === null || row === void 0 ? void 0 : row.key) !== null && _a !== void 0 ? _a : '').trim();
44
+ if (!trimmed)
45
+ return;
46
+ const list = (_b = positions.get(trimmed)) !== null && _b !== void 0 ? _b : [];
47
+ list.push(i);
48
+ positions.set(trimmed, list);
49
+ });
50
+ const dupes = [];
51
+ for (const indices of positions.values()) {
52
+ if (indices.length < 2)
53
+ continue;
54
+ for (const i of indices) {
55
+ dupes.push(this.createError({
56
+ path: `${this.path}[${i}].key`,
57
+ message: 'Duplicate key',
58
+ }));
59
+ }
60
+ }
61
+ if (dupes.length === 0)
62
+ return true;
63
+ return new Yup.ValidationError(dupes);
64
+ }),
15
65
  });
16
66
  export const useForm = (onSubmit) => {
17
67
  const formik = useFormik({
@@ -38,12 +88,71 @@ export const useForm = (onSubmit) => {
38
88
  pgRestoreCustomOptions: '',
39
89
  dumpIgnoreErrors: false,
40
90
  restoreIgnoreErrors: false,
91
+ retrievalMode: 'logical',
92
+ physicalTool: '',
93
+ physicalDockerImage: '',
94
+ physicalSyncEnabled: false,
95
+ physicalWalgBackupName: '',
96
+ physicalPgbackrestStanza: '',
97
+ physicalPgbackrestDelta: false,
98
+ physicalEnvs: [],
41
99
  },
42
100
  validationSchema: Schema,
43
101
  onSubmit,
44
102
  validateOnBlur: false,
45
103
  validateOnChange: false,
46
104
  });
105
+ const [originalPortWasUnset, setOriginalPortWasUnset] = useState(false);
106
+ const [portDirty, setPortDirty] = useState(false);
107
+ const [connectionStringError, setConnectionStringError] = useState(null);
108
+ const connectionString = useMemo(() => connectionStringFromFields({
109
+ host: formik.values.host,
110
+ port: formik.values.port || '5432',
111
+ username: formik.values.username,
112
+ dbname: formik.values.dbname,
113
+ }, { omitDefaultPort: originalPortWasUnset && !portDirty }), [
114
+ formik.values.host,
115
+ formik.values.port,
116
+ formik.values.username,
117
+ formik.values.dbname,
118
+ originalPortWasUnset,
119
+ portDirty,
120
+ ]);
121
+ const onConnectionStringChange = (s) => {
122
+ if (!s.trim()) {
123
+ setConnectionStringError(null);
124
+ formik.setValues({
125
+ ...formik.values,
126
+ host: '',
127
+ port: '',
128
+ username: '',
129
+ dbname: '',
130
+ });
131
+ return;
132
+ }
133
+ try {
134
+ const parsed = connectionStringToFields(s);
135
+ setConnectionStringError(null);
136
+ if (parsed.portWasExplicit)
137
+ setPortDirty(true);
138
+ formik.setValues({
139
+ ...formik.values,
140
+ host: parsed.fields.host,
141
+ port: parsed.fields.port,
142
+ username: parsed.fields.username,
143
+ dbname: parsed.fields.dbname,
144
+ });
145
+ }
146
+ catch (err) {
147
+ if (err instanceof ConnectionStringError)
148
+ setConnectionStringError(err.message);
149
+ else
150
+ throw err;
151
+ }
152
+ };
153
+ const markPortInitialState = (wasUnset) => setOriginalPortWasUnset(wasUnset);
154
+ const markPortDirty = () => setPortDirty(true);
155
+ const omitPortOnSubmit = originalPortWasUnset && !portDirty;
47
156
  const formatDatabaseArray = (database) => {
48
157
  let databases = [];
49
158
  const splitDatabaseArray = database.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
@@ -71,5 +180,17 @@ export const useForm = (onSubmit) => {
71
180
  formik.values.port &&
72
181
  formik.values.username &&
73
182
  formik.values.dbname;
74
- return [{ formik, connectionData, isConnectionDataValid }];
183
+ return [
184
+ {
185
+ formik,
186
+ connectionData,
187
+ isConnectionDataValid,
188
+ connectionString,
189
+ connectionStringError,
190
+ onConnectionStringChange,
191
+ markPortInitialState,
192
+ markPortDirty,
193
+ omitPortOnSubmit,
194
+ },
195
+ ];
75
196
  };
@@ -1,22 +1,6 @@
1
1
  import { dockerImageOptions } from '../configOptions';
2
+ import { dockerImagesConfig, genericImagePrefix } from '../dockerCatalog';
2
3
  const seContainerRegistry = 'se-images';
3
- const genericImagePrefix = 'postgresai/extended-postgres';
4
- // Predefined list of Docker images for UI display
5
- // This list is shown to users for convenient selection
6
- // IMPORTANT: if user specified an image in config that's not in this list,
7
- // it will be automatically added via createEnhancedDockerImages()
8
- const dockerImagesConfig = {
9
- '9.6': ['0.5.3', '0.5.2', '0.5.1'],
10
- '10': ['0.5.3', '0.5.2', '0.5.1'],
11
- '11': ['0.5.3', '0.5.2', '0.5.1'],
12
- '12': ['0.5.3', '0.5.2', '0.5.1'],
13
- '13': ['0.5.3', '0.5.2', '0.5.1'],
14
- '14': ['0.5.3', '0.5.2', '0.5.1'],
15
- '15': ['0.5.3', '0.5.2', '0.5.1'],
16
- '16': ['0.5.3', '0.5.2', '0.5.1'],
17
- '17': ['0.5.3', '0.5.2', '0.5.1'],
18
- '18': ['0.6.1'],
19
- };
20
4
  export const uniqueChipValue = (values) => {
21
5
  const splitChipArray = values.split(/[,(\s)(\n)(\r)(\t)(\r\n)]/);
22
6
  let databaseArray = [];
@@ -16,7 +16,7 @@ const useStyles = makeStyles({
16
16
  position: 'relative',
17
17
  cursor: 'default',
18
18
  flex: `0 0 ${CELL_SIZE}px`,
19
- background: '#f4f4f4',
19
+ background: 'rgba(128, 128, 128, 0.1)',
20
20
  height: `${CELL_SIZE}px`,
21
21
  display: 'flex',
22
22
  borderRadius: `${CELL_SIZE / 2}px`,
@@ -26,7 +26,7 @@ const useStyles = makeStyles({
26
26
  fontSize: '12px',
27
27
  },
28
28
  rootHasSnapshots: {
29
- background: colors.secondary2.lightLight,
29
+ background: 'rgba(15, 135, 157, 0.32)',
30
30
  cursor: 'pointer',
31
31
  },
32
32
  rootCurrent: {
@@ -41,7 +41,7 @@ const useStyles = makeStyles({
41
41
  right: '-6px',
42
42
  position: 'absolute',
43
43
  fontSize: '8px',
44
- backgroundColor: colors.white,
44
+ backgroundColor: 'inherit',
45
45
  border: `1px solid ${colors.secondary2.lightLight}`,
46
46
  borderRadius: '8px',
47
47
  height: '16px',
@@ -17,6 +17,9 @@ export declare const getCalendar: (monthStartDate: Date, snapshots: Snapshot[])
17
17
  logicalSize: number;
18
18
  message: string;
19
19
  branch: string;
20
+ protected: boolean;
21
+ protectedTill?: string | undefined;
22
+ deleteAt?: string | undefined;
20
23
  }[];
21
24
  isBreak: boolean;
22
25
  isDisabled: boolean;
@@ -16,6 +16,9 @@ export declare const getEdgeSnapshots: (snapshots: Snapshot[]) => {
16
16
  logicalSize: number;
17
17
  message: string;
18
18
  branch: string;
19
+ protected: boolean;
20
+ protectedTill?: string | undefined;
21
+ deleteAt?: string | undefined;
19
22
  };
20
23
  lastSnapshot: {
21
24
  createdAtDate: Date;
@@ -30,5 +33,8 @@ export declare const getEdgeSnapshots: (snapshots: Snapshot[]) => {
30
33
  logicalSize: number;
31
34
  message: string;
32
35
  branch: string;
36
+ protected: boolean;
37
+ protectedTill?: string | undefined;
38
+ deleteAt?: string | undefined;
33
39
  };
34
40
  };
@@ -41,6 +41,16 @@ const useStyles = makeStyles({
41
41
  header: {
42
42
  fontWeight: 500,
43
43
  },
44
+ protectedBadge: {
45
+ marginLeft: '8px',
46
+ padding: '1px 6px',
47
+ fontSize: '11px',
48
+ fontWeight: 400,
49
+ color: '#1a7f37',
50
+ border: '1px solid #1a7f37',
51
+ borderRadius: '4px',
52
+ whiteSpace: 'nowrap',
53
+ },
44
54
  infoContent: {
45
55
  fontSize: '12px',
46
56
  color: '#808080',
@@ -113,7 +123,7 @@ const SnapshotListItem = ({ snapshot, setSnapshotModal, openClonesModal, }) => {
113
123
  const timeAgo = formatDistanceSafe(snapshot.createdAtDate);
114
124
  const history = useHistory();
115
125
  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 || '-' }), _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
126
+ return (_jsx("div", { className: classes.commitItem, children: _jsxs("div", { className: classes.gridContainer, children: [_jsxs("div", { className: classes.infoBlock, children: [_jsxs("div", { className: classes.header, children: [snapshot.message || '-', snapshot.protected && (_jsx("span", { className: classes.protectedBadge, title: "Protected from deletion", children: "Protected" }))] }), _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
127
  ? formatBytesIEC(snapshot.physicalSize)
118
128
  : '-' })] }), _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
129
  e.stopPropagation();
@@ -37,6 +37,9 @@ const useStyles = makeStyles({
37
37
  transform: 'translate(-50%, -50%)',
38
38
  },
39
39
  }, { index: 1 });
40
+ // listRefreshIntervalMs is how often the snapshot list is silently refreshed so background
41
+ // auto-deletion does not leave stale rows in the list.
42
+ const listRefreshIntervalMs = 30000;
40
43
  export const Snapshots = observer(({ instanceId }) => {
41
44
  var _a;
42
45
  const host = useHost();
@@ -80,9 +83,14 @@ export const Snapshots = observer(({ instanceId }) => {
80
83
  fetchInitialData();
81
84
  }, []);
82
85
  useEffect(() => {
83
- if (selectedBranch) {
84
- stores.main.reloadSnapshots(selectedBranch === 'All branches' ? '' : selectedBranch);
85
- }
86
+ if (!selectedBranch)
87
+ return;
88
+ const branchName = selectedBranch === 'All branches' ? '' : selectedBranch;
89
+ stores.main.reloadSnapshots(branchName);
90
+ const intervalId = setInterval(() => {
91
+ stores.main.reloadSnapshots(branchName, true);
92
+ }, listRefreshIntervalMs);
93
+ return () => clearInterval(intervalId);
86
94
  }, [selectedBranch]);
87
95
  if (!instance && !snapshots.isLoading)
88
96
  return _jsx(_Fragment, {});
@@ -13,4 +13,7 @@ export declare const groupSnapshotsByCreatedAtDate: (snapshots: Snapshot[]) => {
13
13
  logicalSize: number;
14
14
  message: string;
15
15
  branch: string;
16
+ protected: boolean;
17
+ protectedTill?: string | undefined;
18
+ deleteAt?: string | undefined;
16
19
  }[][];
@@ -1,6 +1,5 @@
1
1
  import { makeStyles } from '@material-ui/core';
2
- import { colors } from '@postgres.ai/shared/styles/colors';
3
- export const useTabsStyles = makeStyles({
2
+ export const useTabsStyles = makeStyles((theme) => ({
4
3
  tabsRoot: {
5
4
  minHeight: 0,
6
5
  marginTop: '-8px',
@@ -21,7 +20,7 @@ export const useTabsStyles = makeStyles({
21
20
  height: '18px',
22
21
  },
23
22
  '& a': {
24
- color: colors.black,
23
+ color: theme.palette.text.primary,
25
24
  textDecoration: 'none',
26
25
  '@media (max-width: 700px)': {
27
26
  display: 'flex',
@@ -39,18 +38,19 @@ export const useTabsStyles = makeStyles({
39
38
  height: '3px',
40
39
  },
41
40
  tabRoot: {
41
+ color: theme.palette.text.primary,
42
42
  fontWeight: 400,
43
43
  minWidth: 0,
44
44
  minHeight: 0,
45
45
  width: '100%',
46
46
  padding: '6px 16px',
47
- borderBottom: `3px solid ${colors.consoleStroke}`,
47
+ borderBottom: `3px solid ${theme.palette.divider}`,
48
48
  '& + $tabRoot': {
49
49
  marginLeft: '10px',
50
50
  },
51
51
  '&.Mui-disabled': {
52
52
  opacity: 1,
53
- color: colors.pgaiDarkGray,
53
+ color: theme.palette.text.disabled,
54
54
  },
55
55
  '@media (max-width: 700px)': {
56
56
  width: 'max-content',
@@ -59,4 +59,4 @@ export const useTabsStyles = makeStyles({
59
59
  tabHidden: {
60
60
  display: 'none',
61
61
  },
62
- }, { index: 1 });
62
+ }), { index: 1 });
@@ -67,7 +67,6 @@ export const Instance = observer((props) => {
67
67
  };
68
68
  const isInstanceIntegrated = instanceRetrieval ||
69
69
  (!isLoadingInstance && !isLoadingInstanceRetrieval && instance && (instance === null || instance === void 0 ? void 0 : instance.url) && !instanceError);
70
- const isConfigurationActive = (instanceRetrieval === null || instanceRetrieval === void 0 ? void 0 : instanceRetrieval.mode) !== 'physical';
71
70
  useEffect(() => {
72
71
  load(instanceId, isPlatform);
73
72
  }, [instanceId]);
@@ -79,14 +78,13 @@ export const Instance = observer((props) => {
79
78
  var _a, _b;
80
79
  if (instance &&
81
80
  ((_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.retrieving) === null || _b === void 0 ? void 0 : _b.status) === 'pending' &&
82
- isConfigurationActive &&
83
81
  !hasBeenRedirected) {
84
82
  setActiveTab(TABS_INDEX.CONFIGURATION);
85
83
  setHasBeenRedirected(true);
86
84
  }
87
85
  }, [instance, hasBeenRedirected]);
88
86
  return (_jsx(HostProvider, { value: props, children: _jsxs(StoresProvider, { value: stores, children: [props.elements.breadcrumbs, _jsx(SectionTitle, { text: props.title, level: 1, tag: "h1", className: classes.title, rightContent: _jsx(Button, { onClick: () => load(props.instanceId, isPlatform), isDisabled: !instance && !instanceError, className: classes.reloadButton, children: "Reload info" }), children: isInstanceIntegrated && (_jsx(InstanceTabs, { instanceId: props.instanceId, tab: activeTab, onTabChange: (tabID) => setActiveTab(tabID), isPlatform: isPlatform, hasLogs: api.initWS !== undefined, hideInstanceTabs: props.hideBranchingFeatures })) }), instanceError && (_jsx(ErrorStub, { ...instanceError, className: classes.errorStub })), isInstanceIntegrated ? (_jsxs(_Fragment, { children: [_jsxs(TabPanel, { value: activeTab, index: TABS_INDEX.OVERVIEW, children: [!instanceError && (_jsx("div", { className: classes.content, children: instance && ((_b = (_a = instance.state) === null || _a === void 0 ? void 0 : _a.retrieving) === null || _b === void 0 ? void 0 : _b.status) ? (_jsxs(_Fragment, { children: [_jsx(Clones, {}), _jsx(Info, { hideBranchingFeatures: props.hideBranchingFeatures })] })) : (_jsx(StubSpinner, {})) })), _jsx(ClonesModal, {}), _jsx(SnapshotsModal, {})] }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CLONES, children: activeTab === TABS_INDEX.CLONES && (_jsx("div", { className: classes.content, children: !instanceError &&
89
- (instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, isConfigurationActive: isConfigurationActive, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org }) })) : (!instanceError && (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx("div", { className: classes.content, children: _jsx(StubSpinner, {}) }) })))] }) }));
87
+ (instance ? _jsx(Clones, { onlyRenderList: true }) : _jsx(StubSpinner, {})) })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.LOGS, children: activeTab === TABS_INDEX.LOGS && (_jsx(Logs, { api: api, instanceId: props.instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.CONFIGURATION, children: activeTab === TABS_INDEX.CONFIGURATION && (_jsx(Configuration, { instanceId: instanceId, switchActiveTab: switchTab, reload: () => load(props.instanceId), disableConfigModification: (_c = instance === null || instance === void 0 ? void 0 : instance.state) === null || _c === void 0 ? void 0 : _c.engine.disableConfigModification })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.SNAPSHOTS, children: activeTab === TABS_INDEX.SNAPSHOTS && (_jsx(Snapshots, { instanceId: instanceId })) }), _jsx(TabPanel, { value: activeTab, index: TABS_INDEX.BRANCHES, children: activeTab === TABS_INDEX.BRANCHES && (_jsx(Branches, { instanceId: instanceId })) })] })) : !isLoadingInstance && !isLoadingInstanceRetrieval && !instanceError ? (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx(InactiveInstance, { instance: instance, org: (_d = props.elements.breadcrumbs) === null || _d === void 0 ? void 0 : _d.props.org }) })) : (!instanceError && (_jsx(TabPanel, { value: activeTab, index: activeTab, children: _jsx("div", { className: classes.content, children: _jsx(StubSpinner, {}) }) })))] }) }));
90
88
  });
91
89
  function TabPanel(props) {
92
90
  const { children, value, index, ...other } = props;
@@ -5,6 +5,7 @@ import { Config } from '@postgres.ai/shared/types/api/entities/config';
5
5
  import { GetConfig } from '@postgres.ai/shared/types/api/endpoints/getConfig';
6
6
  import { UpdateConfig } from '@postgres.ai/shared/types/api/endpoints/updateConfig';
7
7
  import { TestDbSource } from '@postgres.ai/shared/types/api/endpoints/testDbSource';
8
+ import { ProbeSource } from '@postgres.ai/shared/types/api/endpoints/probeSource';
8
9
  import { RefreshInstance } from '@postgres.ai/shared/types/api/endpoints/refreshInstance';
9
10
  import { DestroyClone } from '@postgres.ai/shared/types/api/endpoints/destroyClone';
10
11
  import { ResetClone } from '@postgres.ai/shared/types/api/endpoints/resetClone';
@@ -35,6 +36,7 @@ export declare type Api = {
35
36
  getConfig?: GetConfig;
36
37
  updateConfig?: UpdateConfig;
37
38
  testDbSource?: TestDbSource;
39
+ probeSource?: ProbeSource;
38
40
  getFullConfig?: GetFullConfig;
39
41
  getSeImages?: GetSeImages;
40
42
  getEngine?: GetEngine;
@@ -78,7 +80,7 @@ export declare class MainStore {
78
80
  get isDisabledInstance(): boolean;
79
81
  load: (instanceId: string, isPlatform?: boolean) => void;
80
82
  reload: (instanceId: string) => void;
81
- reloadSnapshots: (branchName?: string) => Promise<void>;
83
+ reloadSnapshots: (branchName?: string, silent?: boolean) => Promise<void>;
82
84
  reloadInstanceRetrieval: () => Promise<void>;
83
85
  private loadInstanceRetrieval;
84
86
  private loadInstance;
@@ -104,6 +106,17 @@ export declare class MainStore {
104
106
  dockerTag?: string | undefined;
105
107
  dockerImageType?: string | undefined;
106
108
  debug: boolean | undefined;
109
+ retrievalMode: import("@postgres.ai/shared/types/api/entities/config").RetrievalMode;
110
+ physicalTool: string;
111
+ physicalDockerImage: string;
112
+ physicalSyncEnabled: boolean;
113
+ physicalWalgBackupName: string;
114
+ physicalPgbackrestStanza: string;
115
+ physicalPgbackrestDelta: boolean;
116
+ physicalEnvs: {
117
+ key: string;
118
+ value: string;
119
+ }[];
107
120
  dockerImage: string | undefined;
108
121
  } | null | undefined>;
109
122
  updateConfig: (values: Config, instanceId: string) => Promise<Response | null | undefined>;
@@ -127,11 +140,18 @@ export declare class MainStore {
127
140
  message: string;
128
141
  };
129
142
  } | undefined>;
143
+ probeSource: (values: {
144
+ url: string;
145
+ password: string;
146
+ }) => Promise<{
147
+ response: import("@postgres.ai/shared/types/api/endpoints/probeSource").ProposedConfig | null;
148
+ error: import("@postgres.ai/shared/types/api/endpoints/probeSource").ProbeSourceError | null;
149
+ } | undefined>;
130
150
  resetClone: (cloneId: string, snapshotId: string) => Promise<boolean | undefined>;
131
151
  destroyClone: (cloneId: string) => Promise<boolean | undefined>;
132
152
  private liveUpdateInstance;
133
153
  reloadClones: () => Promise<void>;
134
- getBranches: (instanceId: string) => Promise<import("@postgres.ai/shared/types/api/endpoints/getBranches").Branch[] | null | undefined>;
154
+ getBranches: (instanceId: string, silent?: boolean) => Promise<import("@postgres.ai/shared/types/api/endpoints/getBranches").Branch[] | null | undefined>;
135
155
  deleteBranch: (branchName: string, instanceId: string) => Promise<{
136
156
  response: Response | null;
137
157
  error: globalThis.Error | null;
@@ -80,10 +80,10 @@ export class MainStore {
80
80
  }
81
81
  });
82
82
  };
83
- this.reloadSnapshots = async (branchName) => {
83
+ this.reloadSnapshots = async (branchName, silent) => {
84
84
  if (!this.instance)
85
85
  return;
86
- await this.snapshots.reload(this.instance.id, branchName);
86
+ await this.snapshots.reload(this.instance.id, branchName, silent);
87
87
  };
88
88
  this.reloadInstanceRetrieval = async () => {
89
89
  if (!this.instance)
@@ -164,15 +164,17 @@ export class MainStore {
164
164
  return response;
165
165
  };
166
166
  this.getFullConfig = async (instanceId) => {
167
- var _a, _b, _c, _d, _e;
167
+ var _a, _b, _c, _d, _e, _f;
168
168
  if (!this.api.getFullConfig)
169
169
  return;
170
170
  const { response, error } = await this.api.getFullConfig(instanceId);
171
171
  if (response) {
172
172
  this.fullConfig = response;
173
173
  const splitYML = this.fullConfig.split('---');
174
- this.platformUrl = (_b = (_a = splitYML[0]) === null || _a === void 0 ? void 0 : _a.split('url: ')[1]) === null || _b === void 0 ? void 0 : _b.split('\n')[0];
175
- this.uiVersion = (_e = (_d = (_c = splitYML[0]) === null || _c === void 0 ? void 0 : _c.split('dockerImage: "postgresai/ce-ui:')[2]) === null || _d === void 0 ? void 0 : _d.split('\n')[0]) === null || _e === void 0 ? void 0 : _e.replace(/['"]+/g, '');
174
+ const platformSection = (_b = (_a = splitYML[0]) === null || _a === void 0 ? void 0 : _a.split('\nplatform:')[1]) !== null && _b !== void 0 ? _b : '';
175
+ const rawPlatformUrl = (_c = platformSection.split('url: ')[1]) === null || _c === void 0 ? void 0 : _c.split('\n')[0];
176
+ this.platformUrl = rawPlatformUrl === null || rawPlatformUrl === void 0 ? void 0 : rawPlatformUrl.replace(/['"]+/g, '').trim().split(/\s+/)[0];
177
+ this.uiVersion = (_f = (_e = (_d = splitYML[0]) === null || _d === void 0 ? void 0 : _d.split('dockerImage: "postgresai/ce-ui:')[2]) === null || _e === void 0 ? void 0 : _e.split('\n')[0]) === null || _f === void 0 ? void 0 : _f.replace(/['"]+/g, '');
176
178
  }
177
179
  if (error)
178
180
  this.getFullConfigError = await error
@@ -216,6 +218,15 @@ export class MainStore {
216
218
  error,
217
219
  };
218
220
  };
221
+ this.probeSource = async (values) => {
222
+ if (!this.api.probeSource)
223
+ return;
224
+ const { response, error } = await this.api.probeSource(values);
225
+ return {
226
+ response,
227
+ error,
228
+ };
229
+ };
219
230
  this.resetClone = async (cloneId, snapshotId) => {
220
231
  if (!this.instance || !this.api.resetClone)
221
232
  return;
@@ -263,13 +274,19 @@ export class MainStore {
263
274
  await this.loadInstanceRetrieval(this.instance.id);
264
275
  this.isReloadingClones = false;
265
276
  };
266
- this.getBranches = async (instanceId) => {
277
+ this.getBranches = async (instanceId, silent) => {
267
278
  if (!this.api.getBranches)
268
279
  return;
269
- this.isBranchesLoading = true;
280
+ if (!silent)
281
+ this.isBranchesLoading = true;
270
282
  const { response, error } = await this.api.getBranches(instanceId);
271
- this.isBranchesLoading = false;
272
- if (error)
283
+ if (!silent)
284
+ this.isBranchesLoading = false;
285
+ if (response)
286
+ this.getBranchesError = null;
287
+ // a silent background refresh must not replace the rendered list with an error stub on a
288
+ // transient failure; errors surface only on a foreground load.
289
+ if (error && !silent)
273
290
  this.getBranchesError = await error.json().then((err) => err);
274
291
  return response;
275
292
  };
@@ -13,11 +13,15 @@ import { makeStyles, Button, TextField, IconButton, Table, TableHead, TableRow,
13
13
  import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub';
14
14
  import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner';
15
15
  import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle';
16
+ import { Select } from '@postgres.ai/shared/components/Select';
17
+ import { Spinner } from '@postgres.ai/shared/components/Spinner';
16
18
  import { DestroySnapshotModal } from '@postgres.ai/shared/pages/Snapshots/Snapshot/DestorySnapshotModal';
17
19
  import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer';
18
20
  import { Tooltip } from '@postgres.ai/shared/components/Tooltip';
19
21
  import { icons } from '@postgres.ai/shared/styles/icons';
20
22
  import { formatBytesIEC } from '@postgres.ai/shared/utils/units';
23
+ import { parseDate } from '@postgres.ai/shared/utils/date';
24
+ import { protectionOptions, getProtectionSelectValue, } from '@postgres.ai/shared/utils/protection';
21
25
  import { styles } from '@postgres.ai/shared/styles/styles';
22
26
  import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight';
23
27
  import { TableBodyCell, TableBodyCellMenu, TableHeaderCell, } from '@postgres.ai/shared/components/Table';
@@ -71,6 +75,13 @@ const useStyles = makeStyles(() => ({
71
75
  summary: {
72
76
  marginTop: 20,
73
77
  },
78
+ protectionControl: {
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ marginTop: '8px',
82
+ marginBottom: '8px',
83
+ maxWidth: 525,
84
+ },
74
85
  text: {
75
86
  marginTop: '4px',
76
87
  },
@@ -129,11 +140,18 @@ export const SnapshotPage = observer((props) => {
129
140
  const history = useHistory();
130
141
  const stores = useCreatedStores(props.api);
131
142
  const [isOpenDestroyModal, setIsOpenDestroyModal] = useState(false);
132
- const { snapshot, branchSnapshot, isSnapshotsLoading, snapshotError, branchSnapshotError, load, } = stores.main;
143
+ const { snapshot, branchSnapshot, isSnapshotsLoading, isUpdatingSnapshot, updateSnapshotProtection, updateSnapshotError, snapshotError, branchSnapshotError, load, } = stores.main;
133
144
  const afterSubmitClick = () => {
134
145
  history.push(props.routes.snapshots());
135
146
  load(props.snapshotId, props.instanceId);
136
147
  };
148
+ const protectedTillDate = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.protectedTill)
149
+ ? parseDate(snapshot.protectedTill)
150
+ : null;
151
+ const handleProtectionChange = (e) => {
152
+ const value = e.target.value;
153
+ updateSnapshotProtection(props.snapshotId, props.instanceId, value === 'none' ? null : parseInt(value, 10));
154
+ };
137
155
  const headRendered = (_jsxs(_Fragment, { children: [_jsx("style", { children: 'p { margin: 0;}' }), props.elements.breadcrumbs, _jsx(SectionTitle, { className: classes.title, tag: "h1", level: 1, text: `Snapshot ${props.snapshotId}`, children: _jsx(InstanceTabs, { tab: TABS_INDEX.SNAPSHOTS, isPlatform: props.isPlatform, instanceId: props.instanceId, hasLogs: props.api.initWS !== undefined, hideInstanceTabs: props.hideBranchingFeatures }) })] }));
138
156
  useEffect(() => {
139
157
  load(props.snapshotId, props.instanceId);
@@ -147,7 +165,15 @@ export const SnapshotPage = observer((props) => {
147
165
  ? formatBytesIEC(snapshot.logicalSize)
148
166
  : '-'] }), _jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Physical data diff size:" }), (snapshot === null || snapshot === void 0 ? void 0 : snapshot.physicalSize)
149
167
  ? formatBytesIEC(snapshot.physicalSize)
150
- : '-'] }), (branchSnapshot === null || branchSnapshot === void 0 ? void 0 : branchSnapshot.message) && (_jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Message:" }), branchSnapshot.message] }))] }), _jsx("br", {}), _jsx("p", { children: _jsx("strong", { children: "Snapshot info" }) }), (snapshot === null || snapshot === void 0 ? void 0 : snapshot.pool) && (_jsxs("div", { className: classes.copyFieldContainer, children: [_jsx(TextField, { variant: "outlined", label: "snapshot pool", value: snapshot.pool, className: classes.textField, margin: "normal", fullWidth: true, InputLabelProps: {
168
+ : '-'] }), (branchSnapshot === null || branchSnapshot === void 0 ? void 0 : branchSnapshot.message) && (_jsxs("p", { className: classes.text, children: [_jsx("span", { className: classes.paramTitle, children: "Message:" }), branchSnapshot.message] }))] }), _jsxs("div", { className: classes.summary, children: [_jsx("p", { children: _jsx("strong", { children: "Deletion protection" }) }), _jsxs("div", { className: classes.protectionControl, children: [_jsx(Select, { label: "Deletion protection", items: (snapshot === null || snapshot === void 0 ? void 0 : snapshot.protected) && protectedTillDate
169
+ ? [
170
+ {
171
+ value: 'current',
172
+ children: `Protected until ${protectedTillDate.toLocaleString()}`,
173
+ },
174
+ ...protectionOptions,
175
+ ]
176
+ : protectionOptions, value: getProtectionSelectValue(Boolean(snapshot === null || snapshot === void 0 ? void 0 : snapshot.protected), protectedTillDate), onChange: handleProtectionChange, disabled: isUpdatingSnapshot, fullWidth: true }), isUpdatingSnapshot && (_jsx(Spinner, { size: "sm", className: classes.spinner }))] }), _jsx("p", { className: classes.text, children: "Protected snapshots cannot be deleted manually or automatically." }), updateSnapshotError && (_jsx(ErrorStub, { title: "Update error", message: updateSnapshotError, className: classes.marginTop }))] }), _jsx("br", {}), _jsx("p", { children: _jsx("strong", { children: "Snapshot info" }) }), (snapshot === null || snapshot === void 0 ? void 0 : snapshot.pool) && (_jsxs("div", { className: classes.copyFieldContainer, children: [_jsx(TextField, { variant: "outlined", label: "snapshot pool", value: snapshot.pool, className: classes.textField, margin: "normal", fullWidth: true, InputLabelProps: {
151
177
  shrink: true,
152
178
  style: styles.inputFieldLabel,
153
179
  }, FormHelperTextProps: {
@@ -1,5 +1,6 @@
1
1
  import { SnapshotsStore, SnapshotsApi } from '@postgres.ai/shared/stores/Snapshots';
2
2
  import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot';
3
+ import { UpdateSnapshot } from '@postgres.ai/shared/types/api/endpoints/updateSnapshot';
3
4
  import { SnapshotDto } from '@postgres.ai/shared/types/api/entities/snapshot';
4
5
  import { GetBranchSnapshot } from '@postgres.ai/shared/types/api/endpoints/getBranchSnapshot';
5
6
  import { BranchSnapshotDto } from '@postgres.ai/shared/types/api/entities/branchSnapshot';
@@ -10,6 +11,7 @@ declare type Error = {
10
11
  };
11
12
  export declare type Api = SnapshotsApi & {
12
13
  destroySnapshot: DestroySnapshot;
14
+ updateSnapshot?: UpdateSnapshot;
13
15
  getBranchSnapshot?: GetBranchSnapshot;
14
16
  initWS?: InitWS;
15
17
  };
@@ -19,12 +21,15 @@ export declare class MainStore {
19
21
  snapshotError: Error | null;
20
22
  branchSnapshotError: Error | null;
21
23
  isSnapshotsLoading: boolean;
24
+ isUpdatingSnapshot: boolean;
25
+ updateSnapshotError: string | null;
22
26
  private readonly api;
23
27
  readonly snapshots: SnapshotsStore;
24
28
  constructor(api: Api);
25
29
  load: (snapshotId: string, instanceId: string) => Promise<void>;
26
30
  getSnapshot: (snapshotId: string, instanceId: string) => Promise<boolean | undefined>;
27
31
  getBranchSnapshot: (snapshotId: string, instanceId: string) => Promise<import("@postgres.ai/shared/types/api/entities/branchSnapshot").BranchSnapshotDTO | null | undefined>;
32
+ updateSnapshotProtection: (snapshotId: string, instanceId: string, durationMinutes: number | null) => Promise<void>;
28
33
  destroySnapshot: (snapshotId: string, forceDelete: boolean, instanceId: string) => Promise<{
29
34
  response: boolean | null;
30
35
  error: any;