@postgres.ai/shared 4.1.2 → 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.
- package/.gitlab-ci.yml +7 -3
- package/components/UpgradeCloneModal/index.tsx +145 -0
- package/dist/components/UpgradeCloneModal/index.d.ts +11 -0
- package/dist/components/UpgradeCloneModal/index.js +75 -0
- package/dist/package.json +2 -2
- package/dist/pages/Branches/Branch/index.js +27 -2
- package/dist/pages/Branches/Branch/stores/Main.d.ts +5 -0
- package/dist/pages/Branches/Branch/stores/Main.js +29 -0
- package/dist/pages/Branches/components/BranchesTable/index.js +2 -2
- package/dist/pages/Branches/index.js +10 -1
- package/dist/pages/Clone/Status/index.js +3 -0
- package/dist/pages/Clone/index.js +12 -4
- package/dist/pages/Clone/stores/Main.d.ts +9 -1
- package/dist/pages/Clone/stores/Main.js +40 -3
- package/dist/pages/CreateBranch/stores/Main.d.ts +3 -0
- package/dist/pages/CreateClone/index.js +2 -0
- package/dist/pages/CreateClone/stores/Main.d.ts +5 -1
- package/dist/pages/CreateClone/stores/Main.js +8 -3
- package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.d.ts +11 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.js +24 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.js +29 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.d.ts +9 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Sync/index.js +14 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/Walg/index.js +21 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/index.d.ts +10 -0
- package/dist/pages/Instance/Configuration/PhysicalMode/index.js +17 -0
- package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.d.ts +13 -0
- package/dist/pages/Instance/Configuration/SimpleMode/PreviewCard.js +14 -0
- package/dist/pages/Instance/Configuration/SimpleMode/index.d.ts +19 -0
- package/dist/pages/Instance/Configuration/SimpleMode/index.js +166 -0
- package/dist/pages/Instance/Configuration/configMode.d.ts +2 -0
- package/dist/pages/Instance/Configuration/configMode.js +7 -0
- package/dist/pages/Instance/Configuration/configOptions.d.ts +16 -0
- package/dist/pages/Instance/Configuration/configOptions.js +71 -0
- package/dist/pages/Instance/Configuration/connectionString.d.ts +20 -0
- package/dist/pages/Instance/Configuration/connectionString.js +129 -0
- package/dist/pages/Instance/Configuration/dockerCatalog.d.ts +2 -0
- package/dist/pages/Instance/Configuration/dockerCatalog.js +19 -0
- package/dist/pages/Instance/Configuration/index.d.ts +1 -2
- package/dist/pages/Instance/Configuration/index.js +115 -86
- package/dist/pages/Instance/Configuration/useForm.d.ts +20 -0
- package/dist/pages/Instance/Configuration/useForm.js +126 -5
- package/dist/pages/Instance/Configuration/utils/index.js +1 -17
- package/dist/pages/Instance/Info/Snapshots/Calendar/Day/index.js +3 -3
- package/dist/pages/Instance/Info/Snapshots/Calendar/utils.d.ts +3 -0
- package/dist/pages/Instance/Info/Snapshots/utils.d.ts +6 -0
- package/dist/pages/Instance/Snapshots/components/SnapshotsList/index.js +11 -1
- package/dist/pages/Instance/Snapshots/index.js +11 -3
- package/dist/pages/Instance/Snapshots/utils/index.d.ts +3 -0
- package/dist/pages/Instance/Tabs/styles.js +6 -6
- package/dist/pages/Instance/index.js +1 -3
- package/dist/pages/Instance/stores/Main.d.ts +22 -2
- package/dist/pages/Instance/stores/Main.js +26 -9
- package/dist/pages/Snapshots/Snapshot/index.js +28 -2
- package/dist/pages/Snapshots/Snapshot/stores/Main.d.ts +5 -0
- package/dist/pages/Snapshots/Snapshot/stores/Main.js +29 -0
- package/dist/stores/Snapshots.d.ts +1 -1
- package/dist/stores/Snapshots.js +12 -6
- package/dist/types/api/endpoints/getBranches.d.ts +6 -0
- package/dist/types/api/endpoints/probeSource.d.ts +32 -0
- package/dist/types/api/endpoints/probeSource.js +1 -0
- package/dist/types/api/endpoints/testDbSource.d.ts +3 -1
- package/dist/types/api/endpoints/testDbSource.js +6 -0
- package/dist/types/api/endpoints/updateBranch.d.ts +11 -0
- package/dist/types/api/endpoints/updateBranch.js +1 -0
- package/dist/types/api/endpoints/updateSnapshot.d.ts +11 -0
- package/dist/types/api/endpoints/updateSnapshot.js +1 -0
- package/dist/types/api/endpoints/upgradeClone.d.ts +8 -0
- package/dist/types/api/endpoints/upgradeClone.js +1 -0
- package/dist/types/api/entities/branchSnapshots.d.ts +3 -0
- package/dist/types/api/entities/clone.d.ts +9 -2
- package/dist/types/api/entities/config.d.ts +32 -0
- package/dist/types/api/entities/config.js +45 -18
- package/dist/types/api/entities/instance.d.ts +11 -1
- package/dist/types/api/entities/instanceState.d.ts +16 -1
- package/dist/types/api/entities/snapshot.d.ts +6 -0
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.js +6 -0
- package/dist/utils/clone.d.ts +3 -1
- package/dist/utils/clone.js +7 -1
- package/dist/utils/clonePoller.d.ts +12 -0
- package/dist/utils/clonePoller.js +41 -0
- package/dist/utils/date.js +12 -12
- package/dist/utils/protection.d.ts +5 -0
- package/dist/utils/protection.js +23 -0
- package/package.json +2 -2
- package/pages/Branches/Branch/index.tsx +70 -0
- package/pages/Branches/Branch/stores/Main.ts +40 -0
- package/pages/Branches/components/BranchesTable/index.tsx +2 -0
- package/pages/Branches/index.tsx +13 -1
- package/pages/Clone/Status/index.tsx +3 -0
- package/pages/Clone/index.tsx +58 -0
- package/pages/Clone/stores/Main.ts +51 -8
- package/pages/CreateClone/index.tsx +3 -0
- package/pages/CreateClone/stores/Main.ts +10 -8
- package/pages/Instance/Configuration/PhysicalMode/EnvsEditor/index.tsx +129 -0
- package/pages/Instance/Configuration/PhysicalMode/PgBackRest/index.tsx +89 -0
- package/pages/Instance/Configuration/PhysicalMode/Sync/index.tsx +64 -0
- package/pages/Instance/Configuration/PhysicalMode/Walg/index.tsx +63 -0
- package/pages/Instance/Configuration/PhysicalMode/index.tsx +93 -0
- package/pages/Instance/Configuration/SimpleMode/PreviewCard.tsx +176 -0
- package/pages/Instance/Configuration/SimpleMode/index.tsx +303 -0
- package/pages/Instance/Configuration/configMode.ts +12 -0
- package/pages/Instance/Configuration/configOptions.ts +111 -0
- package/pages/Instance/Configuration/connectionString.ts +157 -0
- package/pages/Instance/Configuration/dockerCatalog.ts +21 -0
- package/pages/Instance/Configuration/index.tsx +151 -49
- package/pages/Instance/Configuration/useForm.ts +155 -5
- package/pages/Instance/Configuration/utils/index.ts +1 -17
- package/pages/Instance/Info/Snapshots/Calendar/Day/index.tsx +3 -3
- package/pages/Instance/Snapshots/components/SnapshotsList/index.tsx +21 -1
- package/pages/Instance/Snapshots/index.tsx +15 -5
- package/pages/Instance/Tabs/styles.ts +7 -7
- package/pages/Instance/index.tsx +0 -4
- package/pages/Instance/stores/Main.ts +29 -7
- package/pages/Snapshots/Snapshot/index.tsx +71 -0
- package/pages/Snapshots/Snapshot/stores/Main.ts +40 -0
- package/postgres.ai-shared-4.2.0.tgz +0 -0
- package/stores/Snapshots.ts +16 -7
- package/types/api/endpoints/getBranches.ts +3 -0
- package/types/api/endpoints/probeSource.ts +34 -0
- package/types/api/endpoints/testDbSource.ts +10 -1
- package/types/api/endpoints/updateBranch.ts +11 -0
- package/types/api/endpoints/updateSnapshot.ts +11 -0
- package/types/api/endpoints/upgradeClone.ts +8 -0
- package/types/api/entities/clone.ts +13 -1
- package/types/api/entities/config.ts +46 -0
- package/types/api/entities/instanceState.ts +6 -0
- package/types/api/entities/snapshot.ts +3 -0
- package/utils/api.ts +8 -0
- package/utils/clone.ts +7 -1
- package/utils/clonePoller.ts +47 -0
- package/utils/date.ts +19 -14
- package/utils/protection.ts +25 -0
- package/postgres.ai-shared-4.1.2.tgz +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { makeStyles } from '@material-ui/core'
|
|
2
|
-
import { colors } from '@postgres.ai/shared/styles/colors'
|
|
1
|
+
import { makeStyles, Theme } from '@material-ui/core'
|
|
3
2
|
|
|
4
3
|
export const useTabsStyles = makeStyles(
|
|
5
|
-
{
|
|
4
|
+
(theme: Theme) => ({
|
|
6
5
|
tabsRoot: {
|
|
7
6
|
minHeight: 0,
|
|
8
7
|
marginTop: '-8px',
|
|
@@ -24,7 +23,7 @@ export const useTabsStyles = makeStyles(
|
|
|
24
23
|
height: '18px',
|
|
25
24
|
},
|
|
26
25
|
'& a': {
|
|
27
|
-
color:
|
|
26
|
+
color: theme.palette.text.primary,
|
|
28
27
|
textDecoration: 'none',
|
|
29
28
|
|
|
30
29
|
'@media (max-width: 700px)': {
|
|
@@ -43,18 +42,19 @@ export const useTabsStyles = makeStyles(
|
|
|
43
42
|
height: '3px',
|
|
44
43
|
},
|
|
45
44
|
tabRoot: {
|
|
45
|
+
color: theme.palette.text.primary,
|
|
46
46
|
fontWeight: 400,
|
|
47
47
|
minWidth: 0,
|
|
48
48
|
minHeight: 0,
|
|
49
49
|
width: '100%',
|
|
50
50
|
padding: '6px 16px',
|
|
51
|
-
borderBottom: `3px solid ${
|
|
51
|
+
borderBottom: `3px solid ${theme.palette.divider}`,
|
|
52
52
|
'& + $tabRoot': {
|
|
53
53
|
marginLeft: '10px',
|
|
54
54
|
},
|
|
55
55
|
'&.Mui-disabled': {
|
|
56
56
|
opacity: 1,
|
|
57
|
-
color:
|
|
57
|
+
color: theme.palette.text.disabled,
|
|
58
58
|
},
|
|
59
59
|
'@media (max-width: 700px)': {
|
|
60
60
|
width: 'max-content',
|
|
@@ -63,6 +63,6 @@ export const useTabsStyles = makeStyles(
|
|
|
63
63
|
tabHidden: {
|
|
64
64
|
display: 'none',
|
|
65
65
|
},
|
|
66
|
-
},
|
|
66
|
+
}),
|
|
67
67
|
{ index: 1 },
|
|
68
68
|
)
|
package/pages/Instance/index.tsx
CHANGED
|
@@ -96,8 +96,6 @@ export const Instance = observer((props: Props) => {
|
|
|
96
96
|
instanceRetrieval ||
|
|
97
97
|
(!isLoadingInstance && !isLoadingInstanceRetrieval && instance && instance?.url && !instanceError)
|
|
98
98
|
|
|
99
|
-
const isConfigurationActive = instanceRetrieval?.mode !== 'physical'
|
|
100
|
-
|
|
101
99
|
useEffect(() => {
|
|
102
100
|
load(instanceId, isPlatform)
|
|
103
101
|
}, [instanceId])
|
|
@@ -111,7 +109,6 @@ export const Instance = observer((props: Props) => {
|
|
|
111
109
|
if (
|
|
112
110
|
instance &&
|
|
113
111
|
instance.state?.retrieving?.status === 'pending' &&
|
|
114
|
-
isConfigurationActive &&
|
|
115
112
|
!hasBeenRedirected
|
|
116
113
|
) {
|
|
117
114
|
setActiveTab(TABS_INDEX.CONFIGURATION)
|
|
@@ -193,7 +190,6 @@ export const Instance = observer((props: Props) => {
|
|
|
193
190
|
<Configuration
|
|
194
191
|
instanceId={instanceId}
|
|
195
192
|
switchActiveTab={switchTab}
|
|
196
|
-
isConfigurationActive={isConfigurationActive}
|
|
197
193
|
reload={() => load(props.instanceId)}
|
|
198
194
|
disableConfigModification={
|
|
199
195
|
instance?.state?.engine.disableConfigModification
|
|
@@ -13,6 +13,7 @@ import { Config } from '@postgres.ai/shared/types/api/entities/config'
|
|
|
13
13
|
import { GetConfig } from '@postgres.ai/shared/types/api/endpoints/getConfig'
|
|
14
14
|
import { UpdateConfig } from '@postgres.ai/shared/types/api/endpoints/updateConfig'
|
|
15
15
|
import { TestDbSource } from '@postgres.ai/shared/types/api/endpoints/testDbSource'
|
|
16
|
+
import { ProbeSource } from '@postgres.ai/shared/types/api/endpoints/probeSource'
|
|
16
17
|
import { RefreshInstance } from '@postgres.ai/shared/types/api/endpoints/refreshInstance'
|
|
17
18
|
import { DestroyClone } from '@postgres.ai/shared/types/api/endpoints/destroyClone'
|
|
18
19
|
import { ResetClone } from '@postgres.ai/shared/types/api/endpoints/resetClone'
|
|
@@ -47,6 +48,7 @@ export type Api = {
|
|
|
47
48
|
getConfig?: GetConfig
|
|
48
49
|
updateConfig?: UpdateConfig
|
|
49
50
|
testDbSource?: TestDbSource
|
|
51
|
+
probeSource?: ProbeSource
|
|
50
52
|
getFullConfig?: GetFullConfig
|
|
51
53
|
getSeImages?: GetSeImages
|
|
52
54
|
getEngine?: GetEngine
|
|
@@ -169,9 +171,9 @@ export class MainStore {
|
|
|
169
171
|
})
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
reloadSnapshots = async (branchName?: string) => {
|
|
174
|
+
reloadSnapshots = async (branchName?: string, silent?: boolean) => {
|
|
173
175
|
if (!this.instance) return
|
|
174
|
-
await this.snapshots.reload(this.instance.id, branchName)
|
|
176
|
+
await this.snapshots.reload(this.instance.id, branchName, silent)
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
reloadInstanceRetrieval = async () => {
|
|
@@ -291,7 +293,12 @@ export class MainStore {
|
|
|
291
293
|
this.fullConfig = response
|
|
292
294
|
|
|
293
295
|
const splitYML = this.fullConfig.split('---')
|
|
294
|
-
|
|
296
|
+
const platformSection = splitYML[0]?.split('\nplatform:')[1] ?? ''
|
|
297
|
+
const rawPlatformUrl = platformSection.split('url: ')[1]?.split('\n')[0]
|
|
298
|
+
this.platformUrl = rawPlatformUrl
|
|
299
|
+
?.replace(/['"]+/g, '')
|
|
300
|
+
.trim()
|
|
301
|
+
.split(/\s+/)[0]
|
|
295
302
|
this.uiVersion = splitYML[0]
|
|
296
303
|
?.split('dockerImage: "postgresai/ce-ui:')[2]
|
|
297
304
|
?.split('\n')[0]
|
|
@@ -351,6 +358,17 @@ export class MainStore {
|
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
|
|
361
|
+
probeSource = async (values: { url: string; password: string }) => {
|
|
362
|
+
if (!this.api.probeSource) return
|
|
363
|
+
|
|
364
|
+
const { response, error } = await this.api.probeSource(values)
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
response,
|
|
368
|
+
error,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
354
372
|
resetClone = async (cloneId: string, snapshotId: string) => {
|
|
355
373
|
if (!this.instance || !this.api.resetClone) return
|
|
356
374
|
|
|
@@ -402,15 +420,19 @@ export class MainStore {
|
|
|
402
420
|
this.isReloadingClones = false
|
|
403
421
|
}
|
|
404
422
|
|
|
405
|
-
getBranches = async (instanceId: string) => {
|
|
423
|
+
getBranches = async (instanceId: string, silent?: boolean) => {
|
|
406
424
|
if (!this.api.getBranches) return
|
|
407
|
-
this.isBranchesLoading = true
|
|
425
|
+
if (!silent) this.isBranchesLoading = true
|
|
408
426
|
|
|
409
427
|
const { response, error } = await this.api.getBranches(instanceId)
|
|
410
428
|
|
|
411
|
-
this.isBranchesLoading = false
|
|
429
|
+
if (!silent) this.isBranchesLoading = false
|
|
430
|
+
|
|
431
|
+
if (response) this.getBranchesError = null
|
|
412
432
|
|
|
413
|
-
|
|
433
|
+
// a silent background refresh must not replace the rendered list with an error stub on a
|
|
434
|
+
// transient failure; errors surface only on a foreground load.
|
|
435
|
+
if (error && !silent) this.getBranchesError = await error.json().then((err) => err)
|
|
414
436
|
|
|
415
437
|
return response
|
|
416
438
|
}
|
|
@@ -23,11 +23,18 @@ import {
|
|
|
23
23
|
import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
|
|
24
24
|
import { PageSpinner } from '@postgres.ai/shared/components/PageSpinner'
|
|
25
25
|
import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
|
|
26
|
+
import { Select } from '@postgres.ai/shared/components/Select'
|
|
27
|
+
import { Spinner } from '@postgres.ai/shared/components/Spinner'
|
|
26
28
|
import { DestroySnapshotModal } from '@postgres.ai/shared/pages/Snapshots/Snapshot/DestorySnapshotModal'
|
|
27
29
|
import { HorizontalScrollContainer } from '@postgres.ai/shared/components/HorizontalScrollContainer'
|
|
28
30
|
import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
|
|
29
31
|
import { icons } from '@postgres.ai/shared/styles/icons'
|
|
30
32
|
import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
|
|
33
|
+
import { parseDate } from '@postgres.ai/shared/utils/date'
|
|
34
|
+
import {
|
|
35
|
+
protectionOptions,
|
|
36
|
+
getProtectionSelectValue,
|
|
37
|
+
} from '@postgres.ai/shared/utils/protection'
|
|
31
38
|
import { styles } from '@postgres.ai/shared/styles/styles'
|
|
32
39
|
import { SyntaxHighlight } from '@postgres.ai/shared/components/SyntaxHighlight'
|
|
33
40
|
import {
|
|
@@ -94,6 +101,13 @@ const useStyles = makeStyles(
|
|
|
94
101
|
summary: {
|
|
95
102
|
marginTop: 20,
|
|
96
103
|
},
|
|
104
|
+
protectionControl: {
|
|
105
|
+
display: 'flex',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
marginTop: '8px',
|
|
108
|
+
marginBottom: '8px',
|
|
109
|
+
maxWidth: 525,
|
|
110
|
+
},
|
|
97
111
|
text: {
|
|
98
112
|
marginTop: '4px',
|
|
99
113
|
},
|
|
@@ -160,6 +174,9 @@ export const SnapshotPage = observer((props: Props) => {
|
|
|
160
174
|
snapshot,
|
|
161
175
|
branchSnapshot,
|
|
162
176
|
isSnapshotsLoading,
|
|
177
|
+
isUpdatingSnapshot,
|
|
178
|
+
updateSnapshotProtection,
|
|
179
|
+
updateSnapshotError,
|
|
163
180
|
snapshotError,
|
|
164
181
|
branchSnapshotError,
|
|
165
182
|
load,
|
|
@@ -170,6 +187,19 @@ export const SnapshotPage = observer((props: Props) => {
|
|
|
170
187
|
load(props.snapshotId, props.instanceId)
|
|
171
188
|
}
|
|
172
189
|
|
|
190
|
+
const protectedTillDate = snapshot?.protectedTill
|
|
191
|
+
? parseDate(snapshot.protectedTill)
|
|
192
|
+
: null
|
|
193
|
+
|
|
194
|
+
const handleProtectionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
195
|
+
const value = e.target.value
|
|
196
|
+
updateSnapshotProtection(
|
|
197
|
+
props.snapshotId,
|
|
198
|
+
props.instanceId,
|
|
199
|
+
value === 'none' ? null : parseInt(value, 10),
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
173
203
|
const headRendered = (
|
|
174
204
|
<>
|
|
175
205
|
<style>{'p { margin: 0;}'}</style>
|
|
@@ -291,6 +321,47 @@ export const SnapshotPage = observer((props: Props) => {
|
|
|
291
321
|
</p>
|
|
292
322
|
)}
|
|
293
323
|
</div>
|
|
324
|
+
<div className={classes.summary}>
|
|
325
|
+
<p>
|
|
326
|
+
<strong>Deletion protection</strong>
|
|
327
|
+
</p>
|
|
328
|
+
<div className={classes.protectionControl}>
|
|
329
|
+
<Select
|
|
330
|
+
label="Deletion protection"
|
|
331
|
+
items={
|
|
332
|
+
snapshot?.protected && protectedTillDate
|
|
333
|
+
? [
|
|
334
|
+
{
|
|
335
|
+
value: 'current',
|
|
336
|
+
children: `Protected until ${protectedTillDate.toLocaleString()}`,
|
|
337
|
+
},
|
|
338
|
+
...protectionOptions,
|
|
339
|
+
]
|
|
340
|
+
: protectionOptions
|
|
341
|
+
}
|
|
342
|
+
value={getProtectionSelectValue(
|
|
343
|
+
Boolean(snapshot?.protected),
|
|
344
|
+
protectedTillDate,
|
|
345
|
+
)}
|
|
346
|
+
onChange={handleProtectionChange}
|
|
347
|
+
disabled={isUpdatingSnapshot}
|
|
348
|
+
fullWidth
|
|
349
|
+
/>
|
|
350
|
+
{isUpdatingSnapshot && (
|
|
351
|
+
<Spinner size="sm" className={classes.spinner} />
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
<p className={classes.text}>
|
|
355
|
+
Protected snapshots cannot be deleted manually or automatically.
|
|
356
|
+
</p>
|
|
357
|
+
{updateSnapshotError && (
|
|
358
|
+
<ErrorStub
|
|
359
|
+
title="Update error"
|
|
360
|
+
message={updateSnapshotError}
|
|
361
|
+
className={classes.marginTop}
|
|
362
|
+
/>
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
294
365
|
<br />
|
|
295
366
|
<p>
|
|
296
367
|
<strong>Snapshot info</strong>
|
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
SnapshotsApi,
|
|
13
13
|
} from '@postgres.ai/shared/stores/Snapshots'
|
|
14
14
|
import { DestroySnapshot } from '@postgres.ai/shared/types/api/endpoints/destroySnapshot'
|
|
15
|
+
import { UpdateSnapshot } from '@postgres.ai/shared/types/api/endpoints/updateSnapshot'
|
|
15
16
|
import { SnapshotDto } from '@postgres.ai/shared/types/api/entities/snapshot'
|
|
16
17
|
import { GetBranchSnapshot } from '@postgres.ai/shared/types/api/endpoints/getBranchSnapshot'
|
|
17
18
|
import { BranchSnapshotDto } from '@postgres.ai/shared/types/api/entities/branchSnapshot'
|
|
18
19
|
import { generateSnapshotPageId } from '@postgres.ai/shared/pages/Instance/Snapshots/utils'
|
|
19
20
|
import { InitWS } from '@postgres.ai/shared/types/api/endpoints/initWS'
|
|
21
|
+
import { getTextFromUnknownApiError } from '@postgres.ai/shared/utils/api'
|
|
20
22
|
|
|
21
23
|
type Error = {
|
|
22
24
|
title?: string
|
|
@@ -25,6 +27,7 @@ type Error = {
|
|
|
25
27
|
|
|
26
28
|
export type Api = SnapshotsApi & {
|
|
27
29
|
destroySnapshot: DestroySnapshot
|
|
30
|
+
updateSnapshot?: UpdateSnapshot
|
|
28
31
|
getBranchSnapshot?: GetBranchSnapshot
|
|
29
32
|
initWS?: InitWS
|
|
30
33
|
}
|
|
@@ -37,6 +40,8 @@ export class MainStore {
|
|
|
37
40
|
branchSnapshotError: Error | null = null
|
|
38
41
|
|
|
39
42
|
isSnapshotsLoading = false
|
|
43
|
+
isUpdatingSnapshot = false
|
|
44
|
+
updateSnapshotError: string | null = null
|
|
40
45
|
|
|
41
46
|
private readonly api: Api
|
|
42
47
|
readonly snapshots: SnapshotsStore
|
|
@@ -99,6 +104,41 @@ export class MainStore {
|
|
|
99
104
|
return response
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
updateSnapshotProtection = async (
|
|
108
|
+
snapshotId: string,
|
|
109
|
+
instanceId: string,
|
|
110
|
+
durationMinutes: number | null,
|
|
111
|
+
) => {
|
|
112
|
+
if (!this.api.updateSnapshot || !this.snapshot) return
|
|
113
|
+
|
|
114
|
+
this.isUpdatingSnapshot = true
|
|
115
|
+
this.updateSnapshotError = null
|
|
116
|
+
|
|
117
|
+
const prevProtected = this.snapshot.protected
|
|
118
|
+
const isProtected = durationMinutes !== null
|
|
119
|
+
|
|
120
|
+
this.snapshot.protected = isProtected
|
|
121
|
+
|
|
122
|
+
const { response, error } = await this.api.updateSnapshot({
|
|
123
|
+
instanceId,
|
|
124
|
+
snapshotId: this.snapshot.id,
|
|
125
|
+
snapshot: {
|
|
126
|
+
isProtected,
|
|
127
|
+
protectionDurationMinutes: durationMinutes ?? undefined,
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
if (response) {
|
|
132
|
+
await this.load(snapshotId, instanceId)
|
|
133
|
+
} else if (this.snapshot) {
|
|
134
|
+
this.snapshot.protected = prevProtected
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (error) this.updateSnapshotError = await getTextFromUnknownApiError(error)
|
|
138
|
+
|
|
139
|
+
this.isUpdatingSnapshot = false
|
|
140
|
+
}
|
|
141
|
+
|
|
102
142
|
destroySnapshot = async (
|
|
103
143
|
snapshotId: string,
|
|
104
144
|
forceDelete: boolean,
|
|
Binary file
|
package/stores/Snapshots.ts
CHANGED
|
@@ -40,8 +40,8 @@ export class SnapshotsStore {
|
|
|
40
40
|
return this.loadData(instanceId)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
reload = (instanceId: string, branchName?: string) =>
|
|
44
|
-
this.loadData(instanceId, branchName)
|
|
43
|
+
reload = (instanceId: string, branchName?: string, silent?: boolean) =>
|
|
44
|
+
this.loadData(instanceId, branchName, silent)
|
|
45
45
|
|
|
46
46
|
createSnapshot = async (
|
|
47
47
|
cloneId: string,
|
|
@@ -72,21 +72,30 @@ export class SnapshotsStore {
|
|
|
72
72
|
return response
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
private loadData = async (
|
|
75
|
+
private loadData = async (
|
|
76
|
+
instanceId: string,
|
|
77
|
+
branchName?: string,
|
|
78
|
+
silent?: boolean,
|
|
79
|
+
) => {
|
|
76
80
|
if (!this.api.getSnapshots) return
|
|
77
81
|
|
|
78
|
-
this.isLoading = true
|
|
82
|
+
if (!silent) this.isLoading = true
|
|
79
83
|
|
|
80
84
|
const { response, error } = await this.api.getSnapshots({
|
|
81
85
|
instanceId,
|
|
82
86
|
branchName,
|
|
83
87
|
})
|
|
84
88
|
|
|
85
|
-
this.isLoading = false
|
|
89
|
+
if (!silent) this.isLoading = false
|
|
86
90
|
|
|
87
|
-
if (response)
|
|
91
|
+
if (response) {
|
|
92
|
+
this.data = response
|
|
93
|
+
this.error = null
|
|
94
|
+
}
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
// a silent background refresh keeps the existing list on a transient failure; errors
|
|
97
|
+
// surface only on a foreground load.
|
|
98
|
+
if (error && !silent) {
|
|
90
99
|
this.error = await error
|
|
91
100
|
.json()
|
|
92
101
|
.then((error) => error.details?.split('"message": "')[1]?.split('"')[0])
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ProbeSourceRequest = {
|
|
2
|
+
url: string
|
|
3
|
+
password: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type SourceConnection = {
|
|
7
|
+
host: string
|
|
8
|
+
port: number
|
|
9
|
+
username: string
|
|
10
|
+
dbname: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ProposedConfig = {
|
|
14
|
+
source: SourceConnection
|
|
15
|
+
detectedProvider: string
|
|
16
|
+
dockerImage: string
|
|
17
|
+
dockerTag: string
|
|
18
|
+
pgMajorVersion: number
|
|
19
|
+
databases: string[]
|
|
20
|
+
sharedBuffers: string
|
|
21
|
+
memoryProbed: boolean
|
|
22
|
+
sharedPreloadLibraries: string
|
|
23
|
+
queryTuning: { [key: string]: string }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ProbeSourceError = {
|
|
27
|
+
status: number
|
|
28
|
+
message: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ProbeSource = (req: ProbeSourceRequest) => Promise<{
|
|
32
|
+
response: ProposedConfig | null
|
|
33
|
+
error: ProbeSourceError | null
|
|
34
|
+
}>
|
|
@@ -33,7 +33,16 @@ export const formatTuningParams = (
|
|
|
33
33
|
return formattedTuningParams
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export const formatTuningParamsToObj = (
|
|
36
|
+
export const formatTuningParamsToObj = (
|
|
37
|
+
tuningParams: string | { [key: string]: string } | undefined,
|
|
38
|
+
) => {
|
|
39
|
+
// Simple mode seeds tuningParams as an object (the proposed queryTuning map);
|
|
40
|
+
// Expert mode keeps it as the textarea string. Pass an already-parsed object
|
|
41
|
+
// through unchanged so callers never run .split on a non-string.
|
|
42
|
+
if (tuningParams && typeof tuningParams === 'object') {
|
|
43
|
+
return tuningParams
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
let formattedTuningParams: { [key: string]: string } = {}
|
|
38
47
|
|
|
39
48
|
if (tuningParams) {
|
|
@@ -16,9 +16,21 @@ export type CloneDto = {
|
|
|
16
16
|
id: string
|
|
17
17
|
branch: string
|
|
18
18
|
status: {
|
|
19
|
-
code:
|
|
19
|
+
code:
|
|
20
|
+
| 'OK'
|
|
21
|
+
| 'CREATING'
|
|
22
|
+
| 'DELETING'
|
|
23
|
+
| 'RESETTING'
|
|
24
|
+
| 'UPGRADING'
|
|
25
|
+
| 'WARNING'
|
|
26
|
+
| 'FATAL'
|
|
20
27
|
message: string
|
|
21
28
|
}
|
|
29
|
+
// Set once a clone has been upgraded to another major version; empty means the clone runs
|
|
30
|
+
// the engine-wide image.
|
|
31
|
+
dockerImage?: string
|
|
32
|
+
// PostgreSQL major version the clone is currently running.
|
|
33
|
+
dbVersion?: string
|
|
22
34
|
protected: boolean
|
|
23
35
|
protectedTill?: string
|
|
24
36
|
metadata: {
|
|
@@ -11,10 +11,13 @@ export interface DatabaseType {
|
|
|
11
11
|
[name: string]: string | Object
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export type RetrievalMode = 'logical' | 'physical' | 'unknown' | ''
|
|
15
|
+
|
|
14
16
|
export type configTypes = {
|
|
15
17
|
global?: {
|
|
16
18
|
debug?: boolean
|
|
17
19
|
}
|
|
20
|
+
retrievalMode?: RetrievalMode
|
|
18
21
|
databaseContainer?: {
|
|
19
22
|
dockerImage?: string
|
|
20
23
|
dockerPath?: string
|
|
@@ -54,14 +57,57 @@ export type configTypes = {
|
|
|
54
57
|
configs?: { [key: string]: string }
|
|
55
58
|
}
|
|
56
59
|
}
|
|
60
|
+
physicalRestore?: {
|
|
61
|
+
options?: {
|
|
62
|
+
tool?: string
|
|
63
|
+
dockerImage?: string
|
|
64
|
+
sync?: {
|
|
65
|
+
enabled?: boolean
|
|
66
|
+
}
|
|
67
|
+
walg?: {
|
|
68
|
+
backupName?: string
|
|
69
|
+
}
|
|
70
|
+
pgbackrest?: {
|
|
71
|
+
stanza?: string
|
|
72
|
+
delta?: boolean
|
|
73
|
+
}
|
|
74
|
+
envs?: { [key: string]: string | number | boolean }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
57
77
|
}
|
|
58
78
|
}
|
|
59
79
|
}
|
|
60
80
|
|
|
81
|
+
const formatPhysicalEnvs = (
|
|
82
|
+
envs?: { [key: string]: string | number | boolean } | null,
|
|
83
|
+
): Array<{ key: string; value: string }> => {
|
|
84
|
+
if (!envs) return []
|
|
85
|
+
return Object.entries(envs).map(([key, value]) => ({
|
|
86
|
+
key,
|
|
87
|
+
value: typeof value === 'string' ? value : String(value),
|
|
88
|
+
}))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const inferRetrievalMode = (config: configTypes): RetrievalMode => {
|
|
92
|
+
if (config.retrievalMode) return config.retrievalMode
|
|
93
|
+
if (config.retrieval?.spec?.physicalRestore) return 'physical'
|
|
94
|
+
if (config.retrieval?.spec?.logicalDump) return 'logical'
|
|
95
|
+
return ''
|
|
96
|
+
}
|
|
97
|
+
|
|
61
98
|
export const formatConfig = (config: configTypes) => {
|
|
62
99
|
const dockerImage = config.databaseContainer?.dockerImage
|
|
100
|
+
const physical = config.retrieval?.spec?.physicalRestore?.options
|
|
63
101
|
return {
|
|
64
102
|
debug: config.global?.debug,
|
|
103
|
+
retrievalMode: inferRetrievalMode(config),
|
|
104
|
+
physicalTool: physical?.tool ?? '',
|
|
105
|
+
physicalDockerImage: physical?.dockerImage ?? '',
|
|
106
|
+
physicalSyncEnabled: physical?.sync?.enabled ?? false,
|
|
107
|
+
physicalWalgBackupName: physical?.walg?.backupName ?? '',
|
|
108
|
+
physicalPgbackrestStanza: physical?.pgbackrest?.stanza ?? '',
|
|
109
|
+
physicalPgbackrestDelta: physical?.pgbackrest?.delta ?? false,
|
|
110
|
+
physicalEnvs: formatPhysicalEnvs(physical?.envs),
|
|
65
111
|
dockerImage: isSeDockerImage(dockerImage)
|
|
66
112
|
? getImageMajorVersion(dockerImage)
|
|
67
113
|
: dockerImage && getImageType(dockerImage) === 'Generic Postgres'
|
|
@@ -19,6 +19,12 @@ export type InstanceStateDto = {
|
|
|
19
19
|
code: 'OK' | 'WARNING' | 'NO_RESPONSE'
|
|
20
20
|
message: string
|
|
21
21
|
}
|
|
22
|
+
// Absent on engines that do not report it, which is the same as an upgrade being unavailable.
|
|
23
|
+
cloneUpgrade?: {
|
|
24
|
+
available: boolean
|
|
25
|
+
targetVersion?: number
|
|
26
|
+
reason?: string
|
|
27
|
+
}
|
|
22
28
|
// Fallback for capability with old API.
|
|
23
29
|
clones?: CloneDto[]
|
|
24
30
|
// Fallback for capability with old API.
|
package/utils/api.ts
CHANGED
|
@@ -11,6 +11,14 @@ export const getTextFromUnknownApiError = async (error: Response) => {
|
|
|
11
11
|
try {
|
|
12
12
|
const result = await error.json()
|
|
13
13
|
log(result)
|
|
14
|
+
|
|
15
|
+
// The engine answers with models.Error{code, message}. Showing the serialized object puts
|
|
16
|
+
// `{"code":"BAD_REQUEST","message":"..."}` in front of the user when the message alone is
|
|
17
|
+
// what they need; anything without a usable message still falls back to the raw body.
|
|
18
|
+
if (typeof result?.message === 'string' && result.message !== '') {
|
|
19
|
+
return result.message
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
return JSON.stringify(result)
|
|
15
23
|
} catch (e) {
|
|
16
24
|
// not a json
|
package/utils/clone.ts
CHANGED
|
@@ -13,6 +13,8 @@ const STATUS_CODE_TO_TYPE = {
|
|
|
13
13
|
CREATING: 'waiting' as const,
|
|
14
14
|
DELETING: 'waiting' as const,
|
|
15
15
|
RESETTING: 'waiting' as const,
|
|
16
|
+
UPGRADING: 'waiting' as const,
|
|
17
|
+
WARNING: 'warning' as const,
|
|
16
18
|
FATAL: 'error' as const,
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -27,5 +29,9 @@ export const getCloneStatusText = (statusCode: StatusCode) => {
|
|
|
27
29
|
return capitalize(statusCode)
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// WARNING is terminal too: it means the clone is running, just not on the version that was
|
|
33
|
+
// requested. Leaving it out would make the clone page poll forever after a failed upgrade.
|
|
30
34
|
export const checkIsCloneStable = (clone: Clone) =>
|
|
31
|
-
clone.status.code === 'OK' ||
|
|
35
|
+
clone.status.code === 'OK' ||
|
|
36
|
+
clone.status.code === 'WARNING' ||
|
|
37
|
+
clone.status.code === 'FATAL'
|