@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Button, IconButton, TextField, Typography } from '@material-ui/core'
|
|
2
|
+
import Box from '@mui/material/Box'
|
|
3
|
+
|
|
4
|
+
import { PhysicalEnv } from '../../useForm'
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
envs: PhysicalEnv[]
|
|
8
|
+
onChange: (envs: PhysicalEnv[]) => void
|
|
9
|
+
suggestions?: string[]
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
keyErrors?: (string | undefined)[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// EnvsEditor renders a rows-of-key/value editor with add/remove and a
|
|
15
|
+
// click-to-add suggestion list. Engine consumes envs as a free-form map
|
|
16
|
+
// (physical.go:76, CopyOptions.Envs map[string]string); the UI is a thin
|
|
17
|
+
// surface over that map.
|
|
18
|
+
export const EnvsEditor = ({
|
|
19
|
+
envs,
|
|
20
|
+
onChange,
|
|
21
|
+
suggestions = [],
|
|
22
|
+
disabled,
|
|
23
|
+
keyErrors = [],
|
|
24
|
+
}: Props) => {
|
|
25
|
+
const updateRow = (i: number, patch: Partial<PhysicalEnv>) => {
|
|
26
|
+
const next = envs.slice()
|
|
27
|
+
next[i] = { ...next[i], ...patch }
|
|
28
|
+
onChange(next)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const removeRow = (i: number) => {
|
|
32
|
+
const next = envs.slice()
|
|
33
|
+
next.splice(i, 1)
|
|
34
|
+
onChange(next)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const addRow = (key = '') => {
|
|
38
|
+
onChange([...envs, { key, value: '' }])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const usedKeys = new Set(envs.map((e) => e.key))
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Box mt={1} data-testid="envs-editor">
|
|
45
|
+
<Typography variant="subtitle2">Environment variables</Typography>
|
|
46
|
+
{envs.length === 0 ? (
|
|
47
|
+
<Box mt={1} mb={1}>
|
|
48
|
+
<Typography variant="caption" color="textSecondary">
|
|
49
|
+
No environment variables set. Use suggestions below or click "Add".
|
|
50
|
+
</Typography>
|
|
51
|
+
</Box>
|
|
52
|
+
) : (
|
|
53
|
+
envs.map((env, i) => (
|
|
54
|
+
<Box
|
|
55
|
+
key={i}
|
|
56
|
+
display="flex"
|
|
57
|
+
alignItems="center"
|
|
58
|
+
mt={1}
|
|
59
|
+
data-testid={`envs-row-${i}`}
|
|
60
|
+
>
|
|
61
|
+
<TextField
|
|
62
|
+
size="small"
|
|
63
|
+
label="Name"
|
|
64
|
+
value={env.key}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
error={Boolean(keyErrors[i])}
|
|
67
|
+
helperText={keyErrors[i]}
|
|
68
|
+
onChange={(e) => updateRow(i, { key: e.target.value })}
|
|
69
|
+
inputProps={{ 'data-testid': `envs-key-${i}` }}
|
|
70
|
+
/>
|
|
71
|
+
<Box mx={1}>
|
|
72
|
+
<TextField
|
|
73
|
+
size="small"
|
|
74
|
+
label="Value"
|
|
75
|
+
value={env.value}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
onChange={(e) => updateRow(i, { value: e.target.value })}
|
|
78
|
+
inputProps={{ 'data-testid': `envs-value-${i}` }}
|
|
79
|
+
/>
|
|
80
|
+
</Box>
|
|
81
|
+
<IconButton
|
|
82
|
+
size="small"
|
|
83
|
+
aria-label="remove env"
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
onClick={() => removeRow(i)}
|
|
86
|
+
data-testid={`envs-remove-${i}`}
|
|
87
|
+
>
|
|
88
|
+
×
|
|
89
|
+
</IconButton>
|
|
90
|
+
</Box>
|
|
91
|
+
))
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<Box mt={1}>
|
|
95
|
+
<Button
|
|
96
|
+
size="small"
|
|
97
|
+
variant="outlined"
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
onClick={() => addRow()}
|
|
100
|
+
data-testid="envs-add"
|
|
101
|
+
>
|
|
102
|
+
+ Add
|
|
103
|
+
</Button>
|
|
104
|
+
</Box>
|
|
105
|
+
|
|
106
|
+
{suggestions.length > 0 && (
|
|
107
|
+
<Box mt={1}>
|
|
108
|
+
<Typography variant="caption" color="textSecondary">
|
|
109
|
+
Suggestions:
|
|
110
|
+
</Typography>
|
|
111
|
+
<Box mt={0.5}>
|
|
112
|
+
{suggestions.map((s) => (
|
|
113
|
+
<Button
|
|
114
|
+
key={s}
|
|
115
|
+
size="small"
|
|
116
|
+
variant="text"
|
|
117
|
+
disabled={disabled || usedKeys.has(s)}
|
|
118
|
+
onClick={() => addRow(s)}
|
|
119
|
+
data-testid={`envs-suggest-${s}`}
|
|
120
|
+
>
|
|
121
|
+
{s}
|
|
122
|
+
</Button>
|
|
123
|
+
))}
|
|
124
|
+
</Box>
|
|
125
|
+
</Box>
|
|
126
|
+
)}
|
|
127
|
+
</Box>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Checkbox,
|
|
3
|
+
FormControlLabel,
|
|
4
|
+
TextField,
|
|
5
|
+
Typography,
|
|
6
|
+
} from '@material-ui/core'
|
|
7
|
+
import Box from '@mui/material/Box'
|
|
8
|
+
|
|
9
|
+
import { FormValues, PhysicalEnv } from '../../useForm'
|
|
10
|
+
import { EnvsEditor } from '../EnvsEditor'
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
values: FormValues
|
|
14
|
+
onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void
|
|
15
|
+
disabled?: boolean
|
|
16
|
+
envsKeyErrors?: (string | undefined)[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// pgBackRest exposes two structured fields (stanza, delta); repo paths, S3
|
|
20
|
+
// keys, archive options all live in the envs map. See
|
|
21
|
+
// engine/internal/retrieval/engine/postgres/physical/pgbackrest.go:23-26 and
|
|
22
|
+
// the example envs in config.example.physical_pgbackrest.yml:84-99.
|
|
23
|
+
const PGBACKREST_ENV_SUGGESTIONS = [
|
|
24
|
+
'PGBACKREST_REPO',
|
|
25
|
+
'PGBACKREST_REPO1_TYPE',
|
|
26
|
+
'PGBACKREST_REPO1_PATH',
|
|
27
|
+
'PGBACKREST_REPO1_HOST',
|
|
28
|
+
'PGBACKREST_REPO1_HOST_USER',
|
|
29
|
+
'PGBACKREST_REPO1_S3_BUCKET',
|
|
30
|
+
'PGBACKREST_REPO1_S3_ENDPOINT',
|
|
31
|
+
'PGBACKREST_REPO1_S3_KEY',
|
|
32
|
+
'PGBACKREST_REPO1_S3_KEY_SECRET',
|
|
33
|
+
'PGBACKREST_REPO1_S3_REGION',
|
|
34
|
+
'PGBACKREST_LOG_LEVEL_CONSOLE',
|
|
35
|
+
'PGBACKREST_PROCESS_MAX',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
export const PgBackRest = ({
|
|
39
|
+
values,
|
|
40
|
+
onChange,
|
|
41
|
+
disabled,
|
|
42
|
+
envsKeyErrors,
|
|
43
|
+
}: Props) => {
|
|
44
|
+
const onEnvsChange = (envs: PhysicalEnv[]) => onChange('physicalEnvs', envs)
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Box mt={2} data-testid="pgbackrest-form">
|
|
48
|
+
<Typography variant="subtitle1">pgBackRest</Typography>
|
|
49
|
+
<Box mt={1}>
|
|
50
|
+
<TextField
|
|
51
|
+
fullWidth
|
|
52
|
+
label="Stanza"
|
|
53
|
+
placeholder="my-stanza"
|
|
54
|
+
value={values.physicalPgbackrestStanza}
|
|
55
|
+
disabled={disabled}
|
|
56
|
+
onChange={(e) => onChange('physicalPgbackrestStanza', e.target.value)}
|
|
57
|
+
helperText="Stanza name (must match the stanza configured in your pgBackRest setup)."
|
|
58
|
+
inputProps={{ 'data-testid': 'pgbackrest-stanza' }}
|
|
59
|
+
/>
|
|
60
|
+
</Box>
|
|
61
|
+
<Box mt={1}>
|
|
62
|
+
<FormControlLabel
|
|
63
|
+
control={
|
|
64
|
+
<Checkbox
|
|
65
|
+
checked={values.physicalPgbackrestDelta}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
onChange={(e) =>
|
|
68
|
+
onChange('physicalPgbackrestDelta', e.target.checked)
|
|
69
|
+
}
|
|
70
|
+
inputProps={{
|
|
71
|
+
'aria-label': 'delta',
|
|
72
|
+
'data-testid': 'pgbackrest-delta',
|
|
73
|
+
} as React.InputHTMLAttributes<HTMLInputElement>}
|
|
74
|
+
/>
|
|
75
|
+
}
|
|
76
|
+
label="Delta restore"
|
|
77
|
+
/>
|
|
78
|
+
</Box>
|
|
79
|
+
|
|
80
|
+
<EnvsEditor
|
|
81
|
+
envs={values.physicalEnvs}
|
|
82
|
+
onChange={onEnvsChange}
|
|
83
|
+
suggestions={PGBACKREST_ENV_SUGGESTIONS}
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
keyErrors={envsKeyErrors}
|
|
86
|
+
/>
|
|
87
|
+
</Box>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Checkbox,
|
|
3
|
+
FormControlLabel,
|
|
4
|
+
TextField,
|
|
5
|
+
Typography,
|
|
6
|
+
} from '@material-ui/core'
|
|
7
|
+
import Box from '@mui/material/Box'
|
|
8
|
+
|
|
9
|
+
import { FormValues } from '../../useForm'
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
values: FormValues
|
|
13
|
+
onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Shared section rendered below WAL-G / pgBackRest with the structured fields
|
|
18
|
+
// that apply across all physical sub-tools (sync.enabled and dockerImage). The
|
|
19
|
+
// engine surfaces more knobs (sync.healthCheck, sync.configs, recovery target,
|
|
20
|
+
// custom restore command), but they remain YAML-only for 4.2 to keep the
|
|
21
|
+
// projection footprint flat.
|
|
22
|
+
export const Sync = ({ values, onChange, disabled }: Props) => {
|
|
23
|
+
return (
|
|
24
|
+
<Box mt={2} data-testid="physical-sync">
|
|
25
|
+
<Typography variant="subtitle1">Sync container & image</Typography>
|
|
26
|
+
<Box mt={1}>
|
|
27
|
+
<TextField
|
|
28
|
+
fullWidth
|
|
29
|
+
label="Docker image"
|
|
30
|
+
placeholder="postgresai/extended-postgres:18-0.6.2"
|
|
31
|
+
value={values.physicalDockerImage}
|
|
32
|
+
disabled={disabled}
|
|
33
|
+
onChange={(e) => onChange('physicalDockerImage', e.target.value)}
|
|
34
|
+
helperText="Postgres image for restore/sync containers. Major version must match the source."
|
|
35
|
+
inputProps={{ 'data-testid': 'physical-docker-image' }}
|
|
36
|
+
/>
|
|
37
|
+
</Box>
|
|
38
|
+
<Box mt={1}>
|
|
39
|
+
<FormControlLabel
|
|
40
|
+
control={
|
|
41
|
+
<Checkbox
|
|
42
|
+
checked={values.physicalSyncEnabled}
|
|
43
|
+
disabled={disabled}
|
|
44
|
+
onChange={(e) =>
|
|
45
|
+
onChange('physicalSyncEnabled', e.target.checked)
|
|
46
|
+
}
|
|
47
|
+
inputProps={{
|
|
48
|
+
'aria-label': 'sync enabled',
|
|
49
|
+
'data-testid': 'physical-sync-enabled',
|
|
50
|
+
} as React.InputHTMLAttributes<HTMLInputElement>}
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
label="Run sync container"
|
|
54
|
+
/>
|
|
55
|
+
</Box>
|
|
56
|
+
<Box mt={1}>
|
|
57
|
+
<Typography variant="caption" color="textSecondary">
|
|
58
|
+
For advanced sync settings (health check, sync postgres configs,
|
|
59
|
+
recovery target), edit the YAML config directly.
|
|
60
|
+
</Typography>
|
|
61
|
+
</Box>
|
|
62
|
+
</Box>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { TextField, Typography } from '@material-ui/core'
|
|
2
|
+
import Box from '@mui/material/Box'
|
|
3
|
+
|
|
4
|
+
import { FormValues, PhysicalEnv } from '../../useForm'
|
|
5
|
+
import { EnvsEditor } from '../EnvsEditor'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
values: FormValues
|
|
9
|
+
onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
envsKeyErrors?: (string | undefined)[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// WAL-G has one structured field (BackupName, defaulting to "LATEST"); storage
|
|
15
|
+
// backend, bucket, prefix, and credentials all live in the envs map. See
|
|
16
|
+
// engine/internal/retrieval/engine/postgres/physical/wal_g.go:36-38 and the
|
|
17
|
+
// example envs in config.example.physical_walg.yml:84-86.
|
|
18
|
+
const WALG_ENV_SUGGESTIONS = [
|
|
19
|
+
'WALG_GS_PREFIX',
|
|
20
|
+
'WALG_S3_PREFIX',
|
|
21
|
+
'WALG_FILE_PREFIX',
|
|
22
|
+
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
23
|
+
'AWS_ACCESS_KEY_ID',
|
|
24
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
25
|
+
'AWS_REGION',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
export const Walg = ({ values, onChange, disabled, envsKeyErrors }: Props) => {
|
|
29
|
+
const onEnvsChange = (envs: PhysicalEnv[]) => onChange('physicalEnvs', envs)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box mt={2} data-testid="walg-form">
|
|
33
|
+
<Typography variant="subtitle1">WAL-G</Typography>
|
|
34
|
+
<Box mt={1}>
|
|
35
|
+
<TextField
|
|
36
|
+
fullWidth
|
|
37
|
+
label="Backup name"
|
|
38
|
+
placeholder="LATEST"
|
|
39
|
+
value={values.physicalWalgBackupName}
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
onChange={(e) => onChange('physicalWalgBackupName', e.target.value)}
|
|
42
|
+
helperText='Which backup to restore. "LATEST" picks the most recent.'
|
|
43
|
+
inputProps={{ 'data-testid': 'walg-backup-name' }}
|
|
44
|
+
/>
|
|
45
|
+
</Box>
|
|
46
|
+
|
|
47
|
+
<Box mt={2}>
|
|
48
|
+
<Typography variant="caption" color="textSecondary">
|
|
49
|
+
Storage backend, bucket, prefix, and credentials all live in the envs
|
|
50
|
+
map. Do not paste credentials into the backup name field or any URL.
|
|
51
|
+
</Typography>
|
|
52
|
+
</Box>
|
|
53
|
+
|
|
54
|
+
<EnvsEditor
|
|
55
|
+
envs={values.physicalEnvs}
|
|
56
|
+
onChange={onEnvsChange}
|
|
57
|
+
suggestions={WALG_ENV_SUGGESTIONS}
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
keyErrors={envsKeyErrors}
|
|
60
|
+
/>
|
|
61
|
+
</Box>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FormControl,
|
|
3
|
+
FormControlLabel,
|
|
4
|
+
Radio,
|
|
5
|
+
RadioGroup,
|
|
6
|
+
Typography,
|
|
7
|
+
} from '@material-ui/core'
|
|
8
|
+
import Box from '@mui/material/Box'
|
|
9
|
+
|
|
10
|
+
import { FormValues, PhysicalTool } from '../useForm'
|
|
11
|
+
import { PgBackRest } from './PgBackRest'
|
|
12
|
+
import { Sync } from './Sync'
|
|
13
|
+
import { Walg } from './Walg'
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
values: FormValues
|
|
17
|
+
onChange: <K extends keyof FormValues>(key: K, value: FormValues[K]) => void
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
envsKeyErrors?: (string | undefined)[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Physical-mode restore tool selector + sub-form. Two values surface in the
|
|
23
|
+
// UI: WAL-G and pgBackRest, the two tool values the engine accepts outside
|
|
24
|
+
// customTool. pg_basebackup is invoked via the customTool path and remains
|
|
25
|
+
// YAML-only; when the loaded projection has `tool: customTool` we render a
|
|
26
|
+
// banner instead of the sub-form, keeping the user from accidentally wiping a
|
|
27
|
+
// hand-edited customTool config.
|
|
28
|
+
export const PhysicalMode = ({
|
|
29
|
+
values,
|
|
30
|
+
onChange,
|
|
31
|
+
disabled,
|
|
32
|
+
envsKeyErrors,
|
|
33
|
+
}: Props) => {
|
|
34
|
+
const tool = values.physicalTool
|
|
35
|
+
const isCustomTool = tool === 'customTool'
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Box mt={1} data-testid="physical-mode">
|
|
39
|
+
<Typography variant="subtitle2">Restore tool</Typography>
|
|
40
|
+
{isCustomTool ? (
|
|
41
|
+
<Box mt={1} data-testid="physical-custom-tool-banner">
|
|
42
|
+
<Typography variant="body2" color="textSecondary">
|
|
43
|
+
This config uses a custom restore tool (e.g. pg_basebackup). The UI
|
|
44
|
+
cannot edit customTool configurations — edit the YAML directly.
|
|
45
|
+
</Typography>
|
|
46
|
+
</Box>
|
|
47
|
+
) : (
|
|
48
|
+
<FormControl>
|
|
49
|
+
<RadioGroup
|
|
50
|
+
row
|
|
51
|
+
value={tool}
|
|
52
|
+
onChange={(_, value) =>
|
|
53
|
+
onChange('physicalTool', value as PhysicalTool)
|
|
54
|
+
}
|
|
55
|
+
aria-label="physical restore tool"
|
|
56
|
+
>
|
|
57
|
+
<FormControlLabel
|
|
58
|
+
value="walg"
|
|
59
|
+
control={<Radio disabled={disabled} />}
|
|
60
|
+
label="WAL-G"
|
|
61
|
+
/>
|
|
62
|
+
<FormControlLabel
|
|
63
|
+
value="pgbackrest"
|
|
64
|
+
control={<Radio disabled={disabled} />}
|
|
65
|
+
label="pgBackRest"
|
|
66
|
+
/>
|
|
67
|
+
</RadioGroup>
|
|
68
|
+
</FormControl>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{!isCustomTool && tool === 'walg' && (
|
|
72
|
+
<Walg
|
|
73
|
+
values={values}
|
|
74
|
+
onChange={onChange}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
envsKeyErrors={envsKeyErrors}
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
{!isCustomTool && tool === 'pgbackrest' && (
|
|
80
|
+
<PgBackRest
|
|
81
|
+
values={values}
|
|
82
|
+
onChange={onChange}
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
envsKeyErrors={envsKeyErrors}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{!isCustomTool && tool && (
|
|
89
|
+
<Sync values={values} onChange={onChange} disabled={disabled} />
|
|
90
|
+
)}
|
|
91
|
+
</Box>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Button, Link, Typography } from '@material-ui/core'
|
|
2
|
+
import Box from '@mui/material/Box'
|
|
3
|
+
|
|
4
|
+
import { Spinner } from '@postgres.ai/shared/components/Spinner'
|
|
5
|
+
import { ProposedConfig } from '@postgres.ai/shared/types/api/endpoints/probeSource'
|
|
6
|
+
|
|
7
|
+
import { ResolvedImage, providerKeyToImage } from '../configOptions'
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
proposed: ProposedConfig
|
|
11
|
+
resolved: ResolvedImage
|
|
12
|
+
applying: boolean
|
|
13
|
+
applyError: string | null
|
|
14
|
+
onApply: () => void
|
|
15
|
+
onEdit: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Field = ({ label, value }: { label: string; value: string }) => (
|
|
19
|
+
<Box display="flex" mb={0.5}>
|
|
20
|
+
<Box minWidth={220} fontWeight={600}>
|
|
21
|
+
{label}
|
|
22
|
+
</Box>
|
|
23
|
+
<Box minWidth={0} style={{ overflowWrap: 'anywhere' }}>
|
|
24
|
+
{value}
|
|
25
|
+
</Box>
|
|
26
|
+
</Box>
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const Callout = ({ children }: { children: React.ReactNode }) => (
|
|
30
|
+
<Box
|
|
31
|
+
mt={1}
|
|
32
|
+
p={1}
|
|
33
|
+
bgcolor="#fff8e1"
|
|
34
|
+
borderLeft="4px solid #f5a623"
|
|
35
|
+
fontSize={13}
|
|
36
|
+
style={{ overflowWrap: 'anywhere' }}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</Box>
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
export const PreviewCard = ({
|
|
43
|
+
proposed,
|
|
44
|
+
resolved,
|
|
45
|
+
applying,
|
|
46
|
+
applyError,
|
|
47
|
+
onApply,
|
|
48
|
+
onEdit,
|
|
49
|
+
}: Props) => {
|
|
50
|
+
const mapping = providerKeyToImage(
|
|
51
|
+
proposed.dockerImage,
|
|
52
|
+
proposed.pgMajorVersion,
|
|
53
|
+
)
|
|
54
|
+
const preloadLibraries =
|
|
55
|
+
resolved.sharedPreloadLibraries ?? proposed.sharedPreloadLibraries
|
|
56
|
+
const tuningEntries = Object.entries(proposed.queryTuning ?? {})
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Box
|
|
60
|
+
mt={2}
|
|
61
|
+
p={2}
|
|
62
|
+
border="1px solid #e0e0e0"
|
|
63
|
+
borderRadius={4}
|
|
64
|
+
data-testid="preview-card"
|
|
65
|
+
>
|
|
66
|
+
<Typography variant="h6">Proposed configuration</Typography>
|
|
67
|
+
|
|
68
|
+
<Box mt={2}>
|
|
69
|
+
<Field
|
|
70
|
+
label="Detected provider"
|
|
71
|
+
value={proposed.detectedProvider || 'unknown'}
|
|
72
|
+
/>
|
|
73
|
+
<Field
|
|
74
|
+
label="Docker image"
|
|
75
|
+
value={resolved.dockerPath || mapping.imageType}
|
|
76
|
+
/>
|
|
77
|
+
<Field
|
|
78
|
+
label="Postgres major version"
|
|
79
|
+
value={String(proposed.pgMajorVersion || 'unknown')}
|
|
80
|
+
/>
|
|
81
|
+
<Field
|
|
82
|
+
label="Databases"
|
|
83
|
+
value={proposed.databases?.join(', ') || '(none)'}
|
|
84
|
+
/>
|
|
85
|
+
<Field label="shared_buffers" value={proposed.sharedBuffers || ''} />
|
|
86
|
+
<Field
|
|
87
|
+
label="shared_preload_libraries"
|
|
88
|
+
value={preloadLibraries || ''}
|
|
89
|
+
/>
|
|
90
|
+
</Box>
|
|
91
|
+
|
|
92
|
+
{tuningEntries.length > 0 && (
|
|
93
|
+
<Box mt={2}>
|
|
94
|
+
<Typography variant="subtitle2">Query tuning</Typography>
|
|
95
|
+
<Box component="table" mt={1} style={{ borderCollapse: 'collapse' }}>
|
|
96
|
+
<tbody>
|
|
97
|
+
{tuningEntries.map(([k, v]) => (
|
|
98
|
+
<tr key={k}>
|
|
99
|
+
<td style={{ padding: '2px 16px 2px 0', fontWeight: 600 }}>
|
|
100
|
+
{k}
|
|
101
|
+
</td>
|
|
102
|
+
<td style={{ padding: '2px 0' }}>{v}</td>
|
|
103
|
+
</tr>
|
|
104
|
+
))}
|
|
105
|
+
</tbody>
|
|
106
|
+
</Box>
|
|
107
|
+
</Box>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
<Box mt={2}>
|
|
111
|
+
{(mapping.fallback || proposed.detectedProvider === 'generic') && (
|
|
112
|
+
<Callout>
|
|
113
|
+
Could not detect a managed cloud provider; using the generic
|
|
114
|
+
Postgres image. Switch to Expert mode if your source runs on a
|
|
115
|
+
managed service and we missed it.
|
|
116
|
+
</Callout>
|
|
117
|
+
)}
|
|
118
|
+
{!proposed.memoryProbed && (
|
|
119
|
+
<Callout>
|
|
120
|
+
Could not detect host memory; <code>shared_buffers</code> is set
|
|
121
|
+
to a 1 GB safe default. Adjust in Expert mode if your host
|
|
122
|
+
has more RAM.
|
|
123
|
+
</Callout>
|
|
124
|
+
)}
|
|
125
|
+
<Callout>
|
|
126
|
+
Query tuning is copied from your source. If you use the RDS refresh
|
|
127
|
+
tool, these values may not match production — review in Expert mode
|
|
128
|
+
after the first retrieval run.
|
|
129
|
+
</Callout>
|
|
130
|
+
{resolved.isSe ? (
|
|
131
|
+
<Callout>
|
|
132
|
+
Shipping the <code>{resolved.dockerPath}</code> image with its
|
|
133
|
+
curated <code>shared_preload_libraries</code> preset, so the
|
|
134
|
+
detected extensions are bundled.
|
|
135
|
+
</Callout>
|
|
136
|
+
) : (
|
|
137
|
+
<Callout>
|
|
138
|
+
We'll ship <code>{preloadLibraries}</code>. If the chosen image
|
|
139
|
+
does not bundle one of these libraries, the clone container will
|
|
140
|
+
fail to start with a "could not load library" error — check{' '}
|
|
141
|
+
<code>docker logs dblab_server</code> after Apply.
|
|
142
|
+
</Callout>
|
|
143
|
+
)}
|
|
144
|
+
</Box>
|
|
145
|
+
|
|
146
|
+
<Box mt={2} display="flex" alignItems="center">
|
|
147
|
+
<Button
|
|
148
|
+
variant="contained"
|
|
149
|
+
color="secondary"
|
|
150
|
+
onClick={onApply}
|
|
151
|
+
disabled={applying}
|
|
152
|
+
data-testid="preview-apply"
|
|
153
|
+
>
|
|
154
|
+
Apply & start retrieval
|
|
155
|
+
{applying && <Spinner size="sm" />}
|
|
156
|
+
</Button>
|
|
157
|
+
<Box ml={2}>
|
|
158
|
+
<Link
|
|
159
|
+
component="button"
|
|
160
|
+
type="button"
|
|
161
|
+
onClick={onEdit}
|
|
162
|
+
data-testid="preview-edit"
|
|
163
|
+
>
|
|
164
|
+
Edit before applying
|
|
165
|
+
</Link>
|
|
166
|
+
</Box>
|
|
167
|
+
</Box>
|
|
168
|
+
|
|
169
|
+
{applyError && (
|
|
170
|
+
<Box mt={1} color="#d32f2f" fontSize={13} data-testid="apply-error">
|
|
171
|
+
{applyError}
|
|
172
|
+
</Box>
|
|
173
|
+
)}
|
|
174
|
+
</Box>
|
|
175
|
+
)
|
|
176
|
+
}
|