@trieb.work/payload-plugin-backup-mongodb 0.1.1
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/README.md +319 -0
- package/dist/backup-dashboard.css +1132 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -0
- package/dist/collections/BackupSettings.d.ts +6 -0
- package/dist/collections/BackupSettings.js +91 -0
- package/dist/collections/BackupSettings.js.map +1 -0
- package/dist/collections/BackupTasks.d.ts +2 -0
- package/dist/collections/BackupTasks.js +56 -0
- package/dist/collections/BackupTasks.js.map +1 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.d.ts +7 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.js +90 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.js.map +1 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.d.ts +16 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.js +597 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.d.ts +2 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js +939 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.d.ts +18 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js +267 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.d.ts +1 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.js +276 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.js.map +1 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.d.ts +6 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +551 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +29 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.js +240 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.d.ts +1 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js +3 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -0
- package/dist/components/BackupDashboard/index.client.d.ts +8 -0
- package/dist/components/BackupDashboard/index.client.js +9 -0
- package/dist/components/BackupDashboard/index.client.js.map +1 -0
- package/dist/components/BackupDashboard/index.d.ts +8 -0
- package/dist/components/BackupDashboard/index.js +196 -0
- package/dist/components/BackupDashboard/index.js.map +1 -0
- package/dist/core/archive.d.ts +8 -0
- package/dist/core/archive.js +47 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/backup.d.ts +41 -0
- package/dist/core/backup.js +188 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/backupBlobIO.d.ts +54 -0
- package/dist/core/backupBlobIO.js +179 -0
- package/dist/core/backupBlobIO.js.map +1 -0
- package/dist/core/backupBlobTransfer.d.ts +29 -0
- package/dist/core/backupBlobTransfer.js +85 -0
- package/dist/core/backupBlobTransfer.js.map +1 -0
- package/dist/core/backupSettings.d.ts +35 -0
- package/dist/core/backupSettings.js +95 -0
- package/dist/core/backupSettings.js.map +1 -0
- package/dist/core/backupSourcePreview.d.ts +10 -0
- package/dist/core/backupSourcePreview.js +33 -0
- package/dist/core/backupSourcePreview.js.map +1 -0
- package/dist/core/blobTokenValidate.d.ts +14 -0
- package/dist/core/blobTokenValidate.js +75 -0
- package/dist/core/blobTokenValidate.js.map +1 -0
- package/dist/core/db.d.ts +24 -0
- package/dist/core/db.js +12 -0
- package/dist/core/db.js.map +1 -0
- package/dist/core/restore.d.ts +27 -0
- package/dist/core/restore.js +199 -0
- package/dist/core/restore.js.map +1 -0
- package/dist/core/restorePreview.d.ts +64 -0
- package/dist/core/restorePreview.js +266 -0
- package/dist/core/restorePreview.js.map +1 -0
- package/dist/core/taskProgress.d.ts +26 -0
- package/dist/core/taskProgress.js +73 -0
- package/dist/core/taskProgress.js.map +1 -0
- package/dist/core/vercelBackupCron.d.ts +8 -0
- package/dist/core/vercelBackupCron.js +38 -0
- package/dist/core/vercelBackupCron.js.map +1 -0
- package/dist/endpoints/createBackupEndpoints.d.ts +1 -0
- package/dist/endpoints/createBackupEndpoints.js +3 -0
- package/dist/endpoints/createBackupEndpoints.js.map +1 -0
- package/dist/endpoints/index.d.ts +3 -0
- package/dist/endpoints/index.js +34 -0
- package/dist/endpoints/index.js.map +1 -0
- package/dist/endpoints/paths/admin-backup-download.d.ts +8 -0
- package/dist/endpoints/paths/admin-backup-download.js +72 -0
- package/dist/endpoints/paths/admin-backup-download.js.map +1 -0
- package/dist/endpoints/paths/admin-delete.d.ts +3 -0
- package/dist/endpoints/paths/admin-delete.js +55 -0
- package/dist/endpoints/paths/admin-delete.js.map +1 -0
- package/dist/endpoints/paths/admin-manual.d.ts +3 -0
- package/dist/endpoints/paths/admin-manual.js +61 -0
- package/dist/endpoints/paths/admin-manual.js.map +1 -0
- package/dist/endpoints/paths/admin-preview.d.ts +6 -0
- package/dist/endpoints/paths/admin-preview.js +102 -0
- package/dist/endpoints/paths/admin-preview.js.map +1 -0
- package/dist/endpoints/paths/admin-restore.d.ts +3 -0
- package/dist/endpoints/paths/admin-restore.js +74 -0
- package/dist/endpoints/paths/admin-restore.js.map +1 -0
- package/dist/endpoints/paths/admin-seed.d.ts +3 -0
- package/dist/endpoints/paths/admin-seed.js +47 -0
- package/dist/endpoints/paths/admin-seed.js.map +1 -0
- package/dist/endpoints/paths/admin-settings.d.ts +3 -0
- package/dist/endpoints/paths/admin-settings.js +230 -0
- package/dist/endpoints/paths/admin-settings.js.map +1 -0
- package/dist/endpoints/paths/admin-task.d.ts +3 -0
- package/dist/endpoints/paths/admin-task.js +34 -0
- package/dist/endpoints/paths/admin-task.js.map +1 -0
- package/dist/endpoints/paths/admin-validate-blob-token.d.ts +8 -0
- package/dist/endpoints/paths/admin-validate-blob-token.js +26 -0
- package/dist/endpoints/paths/admin-validate-blob-token.js.map +1 -0
- package/dist/endpoints/paths/cron-list.d.ts +2 -0
- package/dist/endpoints/paths/cron-list.js +33 -0
- package/dist/endpoints/paths/cron-list.js.map +1 -0
- package/dist/endpoints/paths/cron-restore.d.ts +2 -0
- package/dist/endpoints/paths/cron-restore.js +61 -0
- package/dist/endpoints/paths/cron-restore.js.map +1 -0
- package/dist/endpoints/paths/cron-run.d.ts +3 -0
- package/dist/endpoints/paths/cron-run.js +45 -0
- package/dist/endpoints/paths/cron-run.js.map +1 -0
- package/dist/endpoints/shared.d.ts +13 -0
- package/dist/endpoints/shared.js +58 -0
- package/dist/endpoints/shared.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +82 -0
- package/dist/plugin.js.map +1 -0
- package/dist/publicApiPaths.d.ts +19 -0
- package/dist/publicApiPaths.js +20 -0
- package/dist/publicApiPaths.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/backupSelection.d.ts +6 -0
- package/dist/utils/backupSelection.js +16 -0
- package/dist/utils/backupSelection.js.map +1 -0
- package/dist/utils/blobName.d.ts +32 -0
- package/dist/utils/blobName.js +99 -0
- package/dist/utils/blobName.js.map +1 -0
- package/dist/utils/dashboardRoleAccess.d.ts +23 -0
- package/dist/utils/dashboardRoleAccess.js +64 -0
- package/dist/utils/dashboardRoleAccess.js.map +1 -0
- package/dist/utils/dialogBackdrop.d.ts +9 -0
- package/dist/utils/dialogBackdrop.js +17 -0
- package/dist/utils/dialogBackdrop.js.map +1 -0
- package/dist/utils/formatBytes.d.ts +2 -0
- package/dist/utils/formatBytes.js +20 -0
- package/dist/utils/formatBytes.js.map +1 -0
- package/dist/utils/hostname.d.ts +2 -0
- package/dist/utils/hostname.js +17 -0
- package/dist/utils/hostname.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/maskBlobToken.d.ts +11 -0
- package/dist/utils/maskBlobToken.js +42 -0
- package/dist/utils/maskBlobToken.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from '@payloadcms/ui';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { backupPluginPublicApiPaths } from '../../publicApiPaths';
|
|
7
|
+
import { selectedFromSkipMongoNames, skipMongoNamesFromPreview } from '../../utils/backupSelection';
|
|
8
|
+
import { closeNativeDialogOnBackdropPointer } from '../../utils/dialogBackdrop';
|
|
9
|
+
import { CollectionBackupPreviewBody } from './CollectionBackupPreviewBody.client';
|
|
10
|
+
function parseBlobTransferTaskMessage(msg) {
|
|
11
|
+
try {
|
|
12
|
+
const o = JSON.parse(msg);
|
|
13
|
+
if (typeof o.total !== 'number' || typeof o.transferred !== 'number') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
failed: typeof o.failed === 'number' ? o.failed : 0,
|
|
18
|
+
pathname: typeof o.pathname === 'string' ? o.pathname : undefined,
|
|
19
|
+
total: o.total,
|
|
20
|
+
transferred: o.transferred
|
|
21
|
+
};
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const TIP_SETTINGS_SUBTITLE = 'These settings apply to scheduled (cron) backups only. Manual backups use the choices in the manual backup dialog each time you create one.';
|
|
27
|
+
const TIP_TRANSFER_COPY = 'When enabled, objects under backups/ are copied from the previous backup token (or from BLOB_READ_WRITE_TOKEN on first setup) into the new store. When disabled, only the new token is saved — existing backups stay in the old storage and remain reachable with the previous token.';
|
|
28
|
+
const TIP_TRANSFER_DELETE = 'Destructive: when enabled, each blob is removed from the old store only after a successful copy to the new store. Leave disabled to keep a full copy in the previous storage as a safety net.';
|
|
29
|
+
function settingsHelpIconClassName(multiline) {
|
|
30
|
+
return [
|
|
31
|
+
'backup-help-icon',
|
|
32
|
+
'backup-help-icon--settings-tooltip',
|
|
33
|
+
multiline ? 'backup-help-icon--multiline' : ''
|
|
34
|
+
].filter(Boolean).join(' ');
|
|
35
|
+
}
|
|
36
|
+
/** Info icon + CSS tooltip; fixed positioning so scroll/clipping in the dialog body does not hide it. */ const SettingsHelpTip = ({ multiline, tip })=>{
|
|
37
|
+
const elRef = useRef(null);
|
|
38
|
+
const syncTooltipAnchor = useCallback(()=>{
|
|
39
|
+
const el = elRef.current;
|
|
40
|
+
if (!el) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const r = el.getBoundingClientRect();
|
|
44
|
+
el.style.setProperty('--backup-tip-x', `${r.left + r.width / 2}px`);
|
|
45
|
+
el.style.setProperty('--backup-tip-y', `${r.top}px`);
|
|
46
|
+
}, []);
|
|
47
|
+
return /*#__PURE__*/ _jsx("button", {
|
|
48
|
+
"aria-label": tip,
|
|
49
|
+
className: settingsHelpIconClassName(multiline),
|
|
50
|
+
"data-tip": tip,
|
|
51
|
+
onFocus: syncTooltipAnchor,
|
|
52
|
+
onMouseEnter: syncTooltipAnchor,
|
|
53
|
+
ref: elRef,
|
|
54
|
+
title: tip,
|
|
55
|
+
type: "button",
|
|
56
|
+
children: "i"
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
export const BackupSettingsModal = ()=>{
|
|
60
|
+
const router = useRouter();
|
|
61
|
+
const transferOptId = useId();
|
|
62
|
+
const deleteSourceOptId = useId();
|
|
63
|
+
const dialogRef = useRef(null);
|
|
64
|
+
const isPreparingOpenRef = useRef(false);
|
|
65
|
+
const [preview, setPreview] = useState(null);
|
|
66
|
+
const [phase, setPhase] = useState('idle');
|
|
67
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
68
|
+
const [selected, setSelected] = useState({});
|
|
69
|
+
const [backupAllCollections, setBackupAllCollections] = useState(false);
|
|
70
|
+
const [includeMediaBlobs, setIncludeMediaBlobs] = useState(true);
|
|
71
|
+
const [backupsToKeep, setBackupsToKeep] = useState(10);
|
|
72
|
+
const [backupBlobReadWriteToken, setBackupBlobReadWriteToken] = useState('');
|
|
73
|
+
/**
|
|
74
|
+
* When saving a new raw token, copy backup blobs from the old store. Default on so rotating a
|
|
75
|
+
* token automatically migrates existing archives; delete-from-source stays opt-in.
|
|
76
|
+
*/ const [transferBackupBlobs, setTransferBackupBlobs] = useState(true);
|
|
77
|
+
/**
|
|
78
|
+
* When copying blobs into the new store, also delete them from the old store.
|
|
79
|
+
* Default: off — the user must explicitly opt in to destructive behavior.
|
|
80
|
+
*/ const [deleteBackupBlobsFromSource, setDeleteBackupBlobsFromSource] = useState(false);
|
|
81
|
+
const [, setHasExistingBackupBlobToken] = useState(false);
|
|
82
|
+
const [storedBackupBlobAccess, setStoredBackupBlobAccess] = useState(null);
|
|
83
|
+
const [effectiveBackupBlobAccess, setEffectiveBackupBlobAccess] = useState(null);
|
|
84
|
+
const [tokenCheck, setTokenCheck] = useState({
|
|
85
|
+
status: 'idle'
|
|
86
|
+
});
|
|
87
|
+
const [pluginOverridesRetention, setPluginOverridesRetention] = useState(false);
|
|
88
|
+
const [cronInfo, setCronInfo] = useState(undefined);
|
|
89
|
+
const [transferSummary, setTransferSummary] = useState(null);
|
|
90
|
+
const [activeTransfer, setActiveTransfer] = useState(null);
|
|
91
|
+
const [transferLive, setTransferLive] = useState(null);
|
|
92
|
+
const [savePhase, setSavePhase] = useState('idle');
|
|
93
|
+
const [saveError, setSaveError] = useState(null);
|
|
94
|
+
const [isPreparingOpen, setIsPreparingOpen] = useState(false);
|
|
95
|
+
const resetState = useCallback(()=>{
|
|
96
|
+
setPreview(null);
|
|
97
|
+
setPhase('idle');
|
|
98
|
+
setErrorMessage(null);
|
|
99
|
+
setSelected({});
|
|
100
|
+
setBackupAllCollections(false);
|
|
101
|
+
setIncludeMediaBlobs(false);
|
|
102
|
+
setBackupsToKeep(10);
|
|
103
|
+
setBackupBlobReadWriteToken('');
|
|
104
|
+
setTransferBackupBlobs(true);
|
|
105
|
+
setDeleteBackupBlobsFromSource(false);
|
|
106
|
+
setHasExistingBackupBlobToken(false);
|
|
107
|
+
setStoredBackupBlobAccess(null);
|
|
108
|
+
setEffectiveBackupBlobAccess(null);
|
|
109
|
+
setTokenCheck({
|
|
110
|
+
status: 'idle'
|
|
111
|
+
});
|
|
112
|
+
setPluginOverridesRetention(false);
|
|
113
|
+
setCronInfo(undefined);
|
|
114
|
+
setTransferSummary(null);
|
|
115
|
+
setActiveTransfer(null);
|
|
116
|
+
setTransferLive(null);
|
|
117
|
+
setSavePhase('idle');
|
|
118
|
+
setSaveError(null);
|
|
119
|
+
isPreparingOpenRef.current = false;
|
|
120
|
+
setIsPreparingOpen(false);
|
|
121
|
+
}, []);
|
|
122
|
+
useEffect(()=>{
|
|
123
|
+
const el = dialogRef.current;
|
|
124
|
+
if (!el) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const onClose = ()=>resetState();
|
|
128
|
+
el.addEventListener('close', onClose);
|
|
129
|
+
return ()=>el.removeEventListener('close', onClose);
|
|
130
|
+
}, [
|
|
131
|
+
resetState
|
|
132
|
+
]);
|
|
133
|
+
const fetchSettingsAndPreview = useCallback(async ()=>{
|
|
134
|
+
const locale = typeof document !== 'undefined' ? (document.documentElement.lang || 'en').slice(0, 2) : 'en';
|
|
135
|
+
const [settingsRes, previewRes] = await Promise.all([
|
|
136
|
+
fetch(backupPluginPublicApiPaths.adminSettings, {
|
|
137
|
+
method: 'GET'
|
|
138
|
+
}),
|
|
139
|
+
fetch(backupPluginPublicApiPaths.adminBackupPreview, {
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
locale
|
|
142
|
+
}),
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json'
|
|
145
|
+
},
|
|
146
|
+
method: 'POST'
|
|
147
|
+
})
|
|
148
|
+
]);
|
|
149
|
+
const settingsJson = await settingsRes.json();
|
|
150
|
+
if (!settingsRes.ok) {
|
|
151
|
+
throw new Error(settingsJson.error || 'Could not load settings');
|
|
152
|
+
}
|
|
153
|
+
const previewJson = await previewRes.json();
|
|
154
|
+
if (!previewRes.ok) {
|
|
155
|
+
throw new Error(previewJson.error || 'Could not load collections');
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
previewJson,
|
|
159
|
+
settingsJson
|
|
160
|
+
};
|
|
161
|
+
}, []);
|
|
162
|
+
const applyLoadedSettingsPreview = useCallback((settingsJson, previewJson)=>{
|
|
163
|
+
setActiveTransfer(null);
|
|
164
|
+
setTransferLive(null);
|
|
165
|
+
setPreview(previewJson);
|
|
166
|
+
setBackupsToKeep(typeof settingsJson.backupsToKeep === 'number' ? settingsJson.backupsToKeep : 10);
|
|
167
|
+
setBackupBlobReadWriteToken(settingsJson.backupBlobTokenMasked || '');
|
|
168
|
+
setHasExistingBackupBlobToken(settingsJson.hasBackupBlobReadWriteToken === true);
|
|
169
|
+
setStoredBackupBlobAccess(settingsJson.backupBlobAccess ?? null);
|
|
170
|
+
setEffectiveBackupBlobAccess(settingsJson.backupBlobAccessEffective ?? null);
|
|
171
|
+
setTokenCheck({
|
|
172
|
+
status: 'idle'
|
|
173
|
+
});
|
|
174
|
+
setPluginOverridesRetention(settingsJson.pluginBackupsToKeepOverride === true);
|
|
175
|
+
setCronInfo(settingsJson.cron ?? null);
|
|
176
|
+
setIncludeMediaBlobs(settingsJson.includeMediaForCron === true);
|
|
177
|
+
const skip = Array.isArray(settingsJson.skipMongoCollections) ? settingsJson.skipMongoCollections : [];
|
|
178
|
+
setSelected(selectedFromSkipMongoNames(previewJson, skip));
|
|
179
|
+
setBackupAllCollections(skip.length === 0);
|
|
180
|
+
setTransferBackupBlobs(true);
|
|
181
|
+
setDeleteBackupBlobsFromSource(false);
|
|
182
|
+
setPhase('ready');
|
|
183
|
+
}, []);
|
|
184
|
+
const loadPreview = useCallback(async ()=>{
|
|
185
|
+
setPhase('loading');
|
|
186
|
+
setErrorMessage(null);
|
|
187
|
+
setPreview(null);
|
|
188
|
+
setCronInfo(undefined);
|
|
189
|
+
try {
|
|
190
|
+
const { previewJson, settingsJson } = await fetchSettingsAndPreview();
|
|
191
|
+
applyLoadedSettingsPreview(settingsJson, previewJson);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
setPhase('error');
|
|
194
|
+
setErrorMessage(e instanceof Error ? e.message : 'Could not load backup settings');
|
|
195
|
+
}
|
|
196
|
+
}, [
|
|
197
|
+
applyLoadedSettingsPreview,
|
|
198
|
+
fetchSettingsAndPreview
|
|
199
|
+
]);
|
|
200
|
+
const openDialog = useCallback(()=>{
|
|
201
|
+
if (isPreparingOpenRef.current) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
isPreparingOpenRef.current = true;
|
|
205
|
+
setSavePhase('idle');
|
|
206
|
+
setSaveError(null);
|
|
207
|
+
setIsPreparingOpen(true);
|
|
208
|
+
void (async ()=>{
|
|
209
|
+
try {
|
|
210
|
+
const { previewJson, settingsJson } = await fetchSettingsAndPreview();
|
|
211
|
+
applyLoadedSettingsPreview(settingsJson, previewJson);
|
|
212
|
+
dialogRef.current?.showModal();
|
|
213
|
+
} catch (e) {
|
|
214
|
+
setPhase('error');
|
|
215
|
+
setErrorMessage(e instanceof Error ? e.message : 'Could not load backup settings');
|
|
216
|
+
setPreview(null);
|
|
217
|
+
setCronInfo(undefined);
|
|
218
|
+
dialogRef.current?.showModal();
|
|
219
|
+
} finally{
|
|
220
|
+
isPreparingOpenRef.current = false;
|
|
221
|
+
setIsPreparingOpen(false);
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
224
|
+
}, [
|
|
225
|
+
applyLoadedSettingsPreview,
|
|
226
|
+
fetchSettingsAndPreview
|
|
227
|
+
]);
|
|
228
|
+
const skipCollections = useMemo(()=>{
|
|
229
|
+
if (!preview) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
if (backupAllCollections) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
return skipMongoNamesFromPreview(preview, selected);
|
|
236
|
+
}, [
|
|
237
|
+
backupAllCollections,
|
|
238
|
+
preview,
|
|
239
|
+
selected
|
|
240
|
+
]);
|
|
241
|
+
const mediaGroupIncluded = preview ? backupAllCollections || selected['media'] !== false : false;
|
|
242
|
+
const hasMediaBlobOption = Boolean(preview && preview.mediaBlobCandidates > 0 && mediaGroupIncluded);
|
|
243
|
+
const willSaveNewRawToken = useMemo(()=>backupBlobReadWriteToken.trim().length > 0 && !/\*{2,}/.test(backupBlobReadWriteToken), [
|
|
244
|
+
backupBlobReadWriteToken
|
|
245
|
+
]);
|
|
246
|
+
const onChangeBackupBlobToken = useCallback((value)=>{
|
|
247
|
+
setBackupBlobReadWriteToken(value);
|
|
248
|
+
setTokenCheck({
|
|
249
|
+
status: 'idle'
|
|
250
|
+
});
|
|
251
|
+
}, []);
|
|
252
|
+
// Auto-validate a freshly pasted/typed token after a short debounce. The masked token coming
|
|
253
|
+
// from the server (contains "**") never triggers this because `willSaveNewRawToken` stays false.
|
|
254
|
+
useEffect(()=>{
|
|
255
|
+
if (!willSaveNewRawToken) {
|
|
256
|
+
setTokenCheck((prev)=>prev.status === 'idle' ? prev : {
|
|
257
|
+
status: 'idle'
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const candidate = backupBlobReadWriteToken.trim();
|
|
262
|
+
if (!candidate) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
let cancelled = false;
|
|
266
|
+
const timer = window.setTimeout(async ()=>{
|
|
267
|
+
if (cancelled) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
setTokenCheck({
|
|
271
|
+
status: 'checking'
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(backupPluginPublicApiPaths.adminValidateBlobToken, {
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
token: candidate
|
|
277
|
+
}),
|
|
278
|
+
headers: {
|
|
279
|
+
'Content-Type': 'application/json'
|
|
280
|
+
},
|
|
281
|
+
method: 'POST'
|
|
282
|
+
});
|
|
283
|
+
if (cancelled) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const data = await response.json();
|
|
287
|
+
if (cancelled) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (response.ok && data.ok && (data.access === 'public' || data.access === 'private')) {
|
|
291
|
+
setTokenCheck({
|
|
292
|
+
access: data.access,
|
|
293
|
+
status: 'valid'
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
setTokenCheck({
|
|
297
|
+
message: data.error || 'Token rejected',
|
|
298
|
+
status: 'invalid'
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (cancelled) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
setTokenCheck({
|
|
306
|
+
message: error instanceof Error ? error.message : 'Token validation failed',
|
|
307
|
+
status: 'invalid'
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}, 150);
|
|
311
|
+
return ()=>{
|
|
312
|
+
cancelled = true;
|
|
313
|
+
window.clearTimeout(timer);
|
|
314
|
+
};
|
|
315
|
+
}, [
|
|
316
|
+
backupBlobReadWriteToken,
|
|
317
|
+
willSaveNewRawToken
|
|
318
|
+
]);
|
|
319
|
+
const blockSaveForTokenCheck = willSaveNewRawToken && (tokenCheck.status === 'checking' || tokenCheck.status === 'invalid');
|
|
320
|
+
const tokenStatusPill = (()=>{
|
|
321
|
+
if (willSaveNewRawToken) {
|
|
322
|
+
if (tokenCheck.status === 'checking') {
|
|
323
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
324
|
+
className: "backup-item__pill backup-item__pill--neutral",
|
|
325
|
+
children: "Checking…"
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (tokenCheck.status === 'valid' && tokenCheck.access) {
|
|
329
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
330
|
+
className: `backup-item__pill backup-item__pill--${tokenCheck.access === 'private' ? 'cron' : 'manual'}`,
|
|
331
|
+
children: tokenCheck.access === 'private' ? 'Private' : 'Public'
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (tokenCheck.status === 'invalid') {
|
|
335
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
336
|
+
className: "backup-item__pill backup-item__pill--danger",
|
|
337
|
+
children: "Invalid"
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
if (effectiveBackupBlobAccess) {
|
|
343
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
344
|
+
className: `backup-item__pill backup-item__pill--${effectiveBackupBlobAccess === 'private' ? 'cron' : 'manual'}`,
|
|
345
|
+
title: storedBackupBlobAccess ? 'Detected and persisted after last validation' : 'Heuristic fallback — will be re-validated when saving a new token',
|
|
346
|
+
children: effectiveBackupBlobAccess === 'private' ? 'Private' : 'Public'
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
})();
|
|
351
|
+
const includeMediaForCron = useMemo(()=>{
|
|
352
|
+
if (!hasMediaBlobOption) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return includeMediaBlobs;
|
|
356
|
+
}, [
|
|
357
|
+
hasMediaBlobOption,
|
|
358
|
+
includeMediaBlobs
|
|
359
|
+
]);
|
|
360
|
+
const onToggleGroup = useCallback((groupId)=>{
|
|
361
|
+
setSelected((prev)=>{
|
|
362
|
+
const isOn = prev[groupId] !== false;
|
|
363
|
+
return {
|
|
364
|
+
...prev,
|
|
365
|
+
[groupId]: !isOn
|
|
366
|
+
};
|
|
367
|
+
});
|
|
368
|
+
}, []);
|
|
369
|
+
const onToggleBackupAllCollections = useCallback(()=>{
|
|
370
|
+
setBackupAllCollections((prev)=>{
|
|
371
|
+
const next = !prev;
|
|
372
|
+
if (next && preview) {
|
|
373
|
+
setSelected(Object.fromEntries(preview.groups.map((g)=>[
|
|
374
|
+
g.groupId,
|
|
375
|
+
true
|
|
376
|
+
])));
|
|
377
|
+
}
|
|
378
|
+
return next;
|
|
379
|
+
});
|
|
380
|
+
}, [
|
|
381
|
+
preview
|
|
382
|
+
]);
|
|
383
|
+
const save = useCallback(async ()=>{
|
|
384
|
+
if (!preview) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
setSavePhase('saving');
|
|
388
|
+
setSaveError(null);
|
|
389
|
+
setTransferSummary(null);
|
|
390
|
+
setActiveTransfer(null);
|
|
391
|
+
setTransferLive(null);
|
|
392
|
+
try {
|
|
393
|
+
const response = await fetch(backupPluginPublicApiPaths.adminSettings, {
|
|
394
|
+
body: JSON.stringify({
|
|
395
|
+
backupBlobReadWriteToken,
|
|
396
|
+
backupsToKeep,
|
|
397
|
+
deleteBackupBlobsFromSource: transferBackupBlobs && deleteBackupBlobsFromSource,
|
|
398
|
+
includeMediaForCron,
|
|
399
|
+
skipMongoCollections: skipCollections,
|
|
400
|
+
transferBackupBlobs
|
|
401
|
+
}),
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-Type': 'application/json'
|
|
404
|
+
},
|
|
405
|
+
method: 'PATCH'
|
|
406
|
+
});
|
|
407
|
+
const data = await response.json();
|
|
408
|
+
if (!response.ok) {
|
|
409
|
+
throw new Error(data.error || 'Save failed');
|
|
410
|
+
}
|
|
411
|
+
setHasExistingBackupBlobToken(data.hasBackupBlobReadWriteToken === true);
|
|
412
|
+
setBackupBlobReadWriteToken(data.backupBlobTokenMasked || '');
|
|
413
|
+
setStoredBackupBlobAccess(data.backupBlobAccess ?? null);
|
|
414
|
+
setEffectiveBackupBlobAccess(data.backupBlobAccessEffective ?? null);
|
|
415
|
+
setTokenCheck({
|
|
416
|
+
status: 'idle'
|
|
417
|
+
});
|
|
418
|
+
const tr = data.transfer;
|
|
419
|
+
if (tr?.performed && tr.deferred && tr.taskId && tr.pollSecret) {
|
|
420
|
+
setActiveTransfer({
|
|
421
|
+
pollSecret: tr.pollSecret,
|
|
422
|
+
taskId: tr.taskId,
|
|
423
|
+
total: tr.total
|
|
424
|
+
});
|
|
425
|
+
setTransferLive({
|
|
426
|
+
failed: 0,
|
|
427
|
+
total: tr.total,
|
|
428
|
+
transferred: 0
|
|
429
|
+
});
|
|
430
|
+
setSavePhase('transferring');
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
setTransferSummary(tr ?? null);
|
|
434
|
+
setSavePhase('saved');
|
|
435
|
+
router.refresh();
|
|
436
|
+
if (!tr?.performed) {
|
|
437
|
+
window.setTimeout(()=>{
|
|
438
|
+
setSavePhase('idle');
|
|
439
|
+
dialogRef.current?.close();
|
|
440
|
+
}, 800);
|
|
441
|
+
} else {
|
|
442
|
+
window.setTimeout(()=>{
|
|
443
|
+
setSavePhase('idle');
|
|
444
|
+
dialogRef.current?.close();
|
|
445
|
+
}, 1200);
|
|
446
|
+
}
|
|
447
|
+
} catch (e) {
|
|
448
|
+
setSavePhase('error');
|
|
449
|
+
setSaveError(e instanceof Error ? e.message : 'Save failed');
|
|
450
|
+
}
|
|
451
|
+
}, [
|
|
452
|
+
preview,
|
|
453
|
+
backupsToKeep,
|
|
454
|
+
backupBlobReadWriteToken,
|
|
455
|
+
skipCollections,
|
|
456
|
+
includeMediaForCron,
|
|
457
|
+
transferBackupBlobs,
|
|
458
|
+
deleteBackupBlobsFromSource,
|
|
459
|
+
router
|
|
460
|
+
]);
|
|
461
|
+
useEffect(()=>{
|
|
462
|
+
if (!activeTransfer) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
let cancelled = false;
|
|
466
|
+
let intervalId = null;
|
|
467
|
+
let successCloseTimer = null;
|
|
468
|
+
const stopPolling = ()=>{
|
|
469
|
+
if (intervalId != null) {
|
|
470
|
+
window.clearInterval(intervalId);
|
|
471
|
+
intervalId = null;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const pollOnce = async ()=>{
|
|
475
|
+
const res = await fetch(backupPluginPublicApiPaths.adminTask(activeTransfer.taskId), {
|
|
476
|
+
cache: 'no-store',
|
|
477
|
+
headers: {
|
|
478
|
+
Authorization: `Bearer ${activeTransfer.pollSecret}`
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
if (!res.ok || cancelled) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const task = await res.json();
|
|
485
|
+
const parsed = parseBlobTransferTaskMessage(task.message);
|
|
486
|
+
if (parsed) {
|
|
487
|
+
setTransferLive({
|
|
488
|
+
failed: parsed.failed,
|
|
489
|
+
pathname: parsed.pathname,
|
|
490
|
+
total: parsed.total,
|
|
491
|
+
transferred: parsed.transferred
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
if (task.status === 'completed') {
|
|
495
|
+
stopPolling();
|
|
496
|
+
const final = parseBlobTransferTaskMessage(task.message);
|
|
497
|
+
setTransferSummary({
|
|
498
|
+
deferred: false,
|
|
499
|
+
failed: final?.failed ?? 0,
|
|
500
|
+
performed: true,
|
|
501
|
+
skipped: 0,
|
|
502
|
+
total: final?.total ?? activeTransfer.total,
|
|
503
|
+
transferred: final?.transferred ?? 0
|
|
504
|
+
});
|
|
505
|
+
setActiveTransfer(null);
|
|
506
|
+
setTransferLive(null);
|
|
507
|
+
setSavePhase('saved');
|
|
508
|
+
try {
|
|
509
|
+
const settingsRes = await fetch(backupPluginPublicApiPaths.adminSettings, {
|
|
510
|
+
method: 'GET'
|
|
511
|
+
});
|
|
512
|
+
const refreshed = await settingsRes.json();
|
|
513
|
+
if (settingsRes.ok) {
|
|
514
|
+
setBackupBlobReadWriteToken(refreshed.backupBlobTokenMasked || '');
|
|
515
|
+
setHasExistingBackupBlobToken(refreshed.hasBackupBlobReadWriteToken === true);
|
|
516
|
+
}
|
|
517
|
+
} catch {
|
|
518
|
+
/* ignore */ }
|
|
519
|
+
router.refresh();
|
|
520
|
+
successCloseTimer = window.setTimeout(()=>{
|
|
521
|
+
successCloseTimer = null;
|
|
522
|
+
setSavePhase('idle');
|
|
523
|
+
setTransferSummary(null);
|
|
524
|
+
dialogRef.current?.close();
|
|
525
|
+
}, 1400);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (task.status === 'failed') {
|
|
529
|
+
stopPolling();
|
|
530
|
+
setSavePhase('error');
|
|
531
|
+
setSaveError(task.error || task.message || 'Transfer failed');
|
|
532
|
+
setActiveTransfer(null);
|
|
533
|
+
setTransferLive(null);
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
intervalId = window.setInterval(()=>void pollOnce(), 900);
|
|
537
|
+
void pollOnce();
|
|
538
|
+
return ()=>{
|
|
539
|
+
cancelled = true;
|
|
540
|
+
stopPolling();
|
|
541
|
+
if (successCloseTimer != null) {
|
|
542
|
+
window.clearTimeout(successCloseTimer);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}, [
|
|
546
|
+
activeTransfer,
|
|
547
|
+
router
|
|
548
|
+
]);
|
|
549
|
+
const retentionDisabled = pluginOverridesRetention;
|
|
550
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
551
|
+
children: [
|
|
552
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
553
|
+
buttonStyle: "secondary",
|
|
554
|
+
disabled: isPreparingOpen,
|
|
555
|
+
onClick: openDialog,
|
|
556
|
+
size: "small",
|
|
557
|
+
children: isPreparingOpen ? 'Loading…' : 'Backup settings'
|
|
558
|
+
}),
|
|
559
|
+
/*#__PURE__*/ _jsxs("dialog", {
|
|
560
|
+
className: "backup-confirm-dialog backup-confirm-dialog--settings",
|
|
561
|
+
onMouseDown: (e)=>closeNativeDialogOnBackdropPointer(e, dialogRef),
|
|
562
|
+
ref: dialogRef,
|
|
563
|
+
children: [
|
|
564
|
+
/*#__PURE__*/ _jsx("p", {
|
|
565
|
+
className: "backup-confirm-dialog__title",
|
|
566
|
+
children: "Scheduled backup settings"
|
|
567
|
+
}),
|
|
568
|
+
/*#__PURE__*/ _jsx("p", {
|
|
569
|
+
className: "backup-confirm-dialog__subtitle backup-confirm-dialog__subtitle--with-help",
|
|
570
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
571
|
+
className: "backup-confirm-dialog__subtitle-text",
|
|
572
|
+
children: TIP_SETTINGS_SUBTITLE
|
|
573
|
+
})
|
|
574
|
+
}),
|
|
575
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
576
|
+
className: "backup-confirm-dialog__body restore-preview",
|
|
577
|
+
children: [
|
|
578
|
+
phase !== 'idle' ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
579
|
+
children: [
|
|
580
|
+
/*#__PURE__*/ _jsx("p", {
|
|
581
|
+
className: "restore-preview__sticky-heading",
|
|
582
|
+
children: "Schedule"
|
|
583
|
+
}),
|
|
584
|
+
phase === 'loading' ? /*#__PURE__*/ _jsxs("div", {
|
|
585
|
+
"aria-live": "polite",
|
|
586
|
+
className: "restore-preview__loading-block restore-preview__loading-block--section",
|
|
587
|
+
children: [
|
|
588
|
+
/*#__PURE__*/ _jsx("p", {
|
|
589
|
+
className: "restore-preview__status",
|
|
590
|
+
children: "Loading cron configuration…"
|
|
591
|
+
}),
|
|
592
|
+
/*#__PURE__*/ _jsx("div", {
|
|
593
|
+
className: "restore-preview__loading-line"
|
|
594
|
+
}),
|
|
595
|
+
/*#__PURE__*/ _jsx("div", {
|
|
596
|
+
className: "restore-preview__loading-line restore-preview__loading-line--short"
|
|
597
|
+
})
|
|
598
|
+
]
|
|
599
|
+
}) : phase === 'error' ? /*#__PURE__*/ _jsx("p", {
|
|
600
|
+
className: "restore-preview__intro",
|
|
601
|
+
children: "Schedule details could not be loaded."
|
|
602
|
+
}) : cronInfo ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
603
|
+
children: [
|
|
604
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
605
|
+
className: "restore-preview__intro",
|
|
606
|
+
children: [
|
|
607
|
+
"Active job: ",
|
|
608
|
+
/*#__PURE__*/ _jsx("code", {
|
|
609
|
+
children: cronInfo.configFileRelative
|
|
610
|
+
}),
|
|
611
|
+
" — edit and redeploy to change the schedule."
|
|
612
|
+
]
|
|
613
|
+
}),
|
|
614
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
615
|
+
"aria-label": "Configured schedule",
|
|
616
|
+
className: "restore-preview__settings-block",
|
|
617
|
+
children: [
|
|
618
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
619
|
+
className: "restore-preview__settings-row",
|
|
620
|
+
children: [
|
|
621
|
+
/*#__PURE__*/ _jsx("span", {
|
|
622
|
+
className: "restore-preview__settings-k",
|
|
623
|
+
children: "HTTP path"
|
|
624
|
+
}),
|
|
625
|
+
' ',
|
|
626
|
+
/*#__PURE__*/ _jsx("code", {
|
|
627
|
+
children: cronInfo.path
|
|
628
|
+
})
|
|
629
|
+
]
|
|
630
|
+
}),
|
|
631
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
632
|
+
className: "restore-preview__settings-row",
|
|
633
|
+
children: [
|
|
634
|
+
/*#__PURE__*/ _jsx("span", {
|
|
635
|
+
className: "restore-preview__settings-k",
|
|
636
|
+
children: "Cron expression"
|
|
637
|
+
}),
|
|
638
|
+
' ',
|
|
639
|
+
/*#__PURE__*/ _jsx("code", {
|
|
640
|
+
children: cronInfo.schedule
|
|
641
|
+
})
|
|
642
|
+
]
|
|
643
|
+
}),
|
|
644
|
+
cronInfo.humanDescription ? /*#__PURE__*/ _jsx("p", {
|
|
645
|
+
className: "restore-preview__settings-human",
|
|
646
|
+
children: cronInfo.humanDescription
|
|
647
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
648
|
+
className: "restore-preview__settings-human restore-preview__settings-human--muted",
|
|
649
|
+
children: "Could not turn the cron expression into a short description."
|
|
650
|
+
})
|
|
651
|
+
]
|
|
652
|
+
})
|
|
653
|
+
]
|
|
654
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
655
|
+
children: [
|
|
656
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
657
|
+
className: "restore-preview__intro",
|
|
658
|
+
children: [
|
|
659
|
+
"No vercel ",
|
|
660
|
+
/*#__PURE__*/ _jsx("code", {
|
|
661
|
+
children: "crons"
|
|
662
|
+
}),
|
|
663
|
+
" entry for the backup path yet. You can either setup an external service to call /api/backup-mongodb/cron/run with your cron secret or use Vercel. To use vercel create a file ",
|
|
664
|
+
/*#__PURE__*/ _jsx("code", {
|
|
665
|
+
children: "vercel.json"
|
|
666
|
+
}),
|
|
667
|
+
" in your project root with the following content:"
|
|
668
|
+
]
|
|
669
|
+
}),
|
|
670
|
+
/*#__PURE__*/ _jsx("p", {
|
|
671
|
+
className: "restore-preview__settings-hint",
|
|
672
|
+
children: "Example"
|
|
673
|
+
}),
|
|
674
|
+
/*#__PURE__*/ _jsx("pre", {
|
|
675
|
+
className: "restore-preview__settings-snippet",
|
|
676
|
+
children: `{
|
|
677
|
+
"crons": [
|
|
678
|
+
{
|
|
679
|
+
"path": "/api/backup-mongodb/cron/run",
|
|
680
|
+
"schedule": "0 3 * * *"
|
|
681
|
+
}
|
|
682
|
+
]
|
|
683
|
+
}`
|
|
684
|
+
}),
|
|
685
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
686
|
+
children: [
|
|
687
|
+
"Then add a ",
|
|
688
|
+
/*#__PURE__*/ _jsx("code", {
|
|
689
|
+
children: "CRON_SECRET"
|
|
690
|
+
}),
|
|
691
|
+
" environment variable to your Vercel project and redeploy on Vercel so the cron job is registered."
|
|
692
|
+
]
|
|
693
|
+
})
|
|
694
|
+
]
|
|
695
|
+
})
|
|
696
|
+
]
|
|
697
|
+
}) : null,
|
|
698
|
+
/*#__PURE__*/ _jsx("p", {
|
|
699
|
+
className: "restore-preview__sticky-heading",
|
|
700
|
+
children: "Retention"
|
|
701
|
+
}),
|
|
702
|
+
/*#__PURE__*/ _jsx("p", {
|
|
703
|
+
className: "restore-preview__intro",
|
|
704
|
+
children: retentionDisabled ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
705
|
+
children: [
|
|
706
|
+
"Locked by ",
|
|
707
|
+
/*#__PURE__*/ _jsxs("code", {
|
|
708
|
+
children: [
|
|
709
|
+
"backupMongodbPlugin(",
|
|
710
|
+
'{ backupsToKeep }',
|
|
711
|
+
")"
|
|
712
|
+
]
|
|
713
|
+
}),
|
|
714
|
+
" in Payload config."
|
|
715
|
+
]
|
|
716
|
+
}) : /*#__PURE__*/ _jsx(_Fragment, {
|
|
717
|
+
children: "Oldest cron archives are deleted once the count exceeds this limit."
|
|
718
|
+
})
|
|
719
|
+
}),
|
|
720
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
721
|
+
className: "restore-preview__settings-fields",
|
|
722
|
+
children: [
|
|
723
|
+
/*#__PURE__*/ _jsx("label", {
|
|
724
|
+
className: "restore-preview__settings-label",
|
|
725
|
+
htmlFor: "backups-to-keep",
|
|
726
|
+
children: "Cron backups to keep"
|
|
727
|
+
}),
|
|
728
|
+
/*#__PURE__*/ _jsx("input", {
|
|
729
|
+
"aria-label": "Cron backups to keep",
|
|
730
|
+
className: "restore-preview__settings-input",
|
|
731
|
+
disabled: retentionDisabled,
|
|
732
|
+
id: "backups-to-keep",
|
|
733
|
+
max: 365,
|
|
734
|
+
min: 1,
|
|
735
|
+
onChange: (e)=>setBackupsToKeep(Number(e.target.value) || 1),
|
|
736
|
+
type: "number",
|
|
737
|
+
value: backupsToKeep
|
|
738
|
+
})
|
|
739
|
+
]
|
|
740
|
+
}),
|
|
741
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
742
|
+
className: "restore-preview__sticky-heading",
|
|
743
|
+
children: [
|
|
744
|
+
"Dedicated backup storage",
|
|
745
|
+
' ',
|
|
746
|
+
/*#__PURE__*/ _jsx("span", {
|
|
747
|
+
className: "restore-preview__sticky-heading-hint",
|
|
748
|
+
children: "(optional)"
|
|
749
|
+
})
|
|
750
|
+
]
|
|
751
|
+
}),
|
|
752
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
753
|
+
className: "restore-preview__intro",
|
|
754
|
+
children: [
|
|
755
|
+
"Separate Vercel Blob token for ",
|
|
756
|
+
/*#__PURE__*/ _jsx("code", {
|
|
757
|
+
children: "backups/"
|
|
758
|
+
}),
|
|
759
|
+
". Without one, archives share the default media store."
|
|
760
|
+
]
|
|
761
|
+
}),
|
|
762
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
763
|
+
className: "restore-preview__settings-fields",
|
|
764
|
+
children: [
|
|
765
|
+
/*#__PURE__*/ _jsx("label", {
|
|
766
|
+
className: "restore-preview__settings-label",
|
|
767
|
+
htmlFor: "backup-blob-token",
|
|
768
|
+
children: "Backup Blob read/write token"
|
|
769
|
+
}),
|
|
770
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
771
|
+
className: "restore-preview__blob-token-row",
|
|
772
|
+
children: [
|
|
773
|
+
/*#__PURE__*/ _jsx("input", {
|
|
774
|
+
"aria-label": "Backup Blob read/write token",
|
|
775
|
+
autoComplete: "off",
|
|
776
|
+
className: "restore-preview__settings-input restore-preview__settings-input--wide",
|
|
777
|
+
id: "backup-blob-token",
|
|
778
|
+
onChange: (e)=>onChangeBackupBlobToken(e.target.value),
|
|
779
|
+
placeholder: "vercel_blob_rw_...",
|
|
780
|
+
spellCheck: false,
|
|
781
|
+
type: "text",
|
|
782
|
+
value: backupBlobReadWriteToken
|
|
783
|
+
}),
|
|
784
|
+
/*#__PURE__*/ _jsx("span", {
|
|
785
|
+
className: "restore-preview__blob-token-pill-slot",
|
|
786
|
+
children: tokenStatusPill
|
|
787
|
+
})
|
|
788
|
+
]
|
|
789
|
+
}),
|
|
790
|
+
tokenCheck.status === 'invalid' && tokenCheck.message ? /*#__PURE__*/ _jsx("p", {
|
|
791
|
+
"aria-live": "polite",
|
|
792
|
+
className: "restore-preview__blob-token-error",
|
|
793
|
+
children: tokenCheck.message
|
|
794
|
+
}) : null,
|
|
795
|
+
savePhase === 'transferring' && transferLive && transferLive.total > 0 ? /*#__PURE__*/ _jsxs("div", {
|
|
796
|
+
"aria-live": "polite",
|
|
797
|
+
className: "restore-preview__transfer-panel",
|
|
798
|
+
children: [
|
|
799
|
+
/*#__PURE__*/ _jsx("div", {
|
|
800
|
+
className: "restore-preview__transfer-bar-track",
|
|
801
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
802
|
+
className: "restore-preview__transfer-bar-fill",
|
|
803
|
+
style: {
|
|
804
|
+
width: `${Math.min(100, (transferLive.transferred + transferLive.failed) / transferLive.total * 100)}%`
|
|
805
|
+
}
|
|
806
|
+
})
|
|
807
|
+
}),
|
|
808
|
+
/*#__PURE__*/ _jsxs("p", {
|
|
809
|
+
className: "restore-preview__transfer-meta",
|
|
810
|
+
children: [
|
|
811
|
+
"Copying backup files: ",
|
|
812
|
+
transferLive.transferred + transferLive.failed,
|
|
813
|
+
" /",
|
|
814
|
+
' ',
|
|
815
|
+
transferLive.total,
|
|
816
|
+
transferLive.failed > 0 ? ` (${transferLive.failed} failed)` : '',
|
|
817
|
+
transferLive.pathname ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
818
|
+
children: [
|
|
819
|
+
/*#__PURE__*/ _jsx("br", {}),
|
|
820
|
+
/*#__PURE__*/ _jsx("span", {
|
|
821
|
+
className: "restore-preview__transfer-path",
|
|
822
|
+
title: transferLive.pathname,
|
|
823
|
+
children: transferLive.pathname.length > 56 ? `${transferLive.pathname.slice(0, 28)}…${transferLive.pathname.slice(-24)}` : transferLive.pathname
|
|
824
|
+
})
|
|
825
|
+
]
|
|
826
|
+
}) : null
|
|
827
|
+
]
|
|
828
|
+
})
|
|
829
|
+
]
|
|
830
|
+
}) : null,
|
|
831
|
+
willSaveNewRawToken ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
832
|
+
children: [
|
|
833
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
834
|
+
className: "field-type checkbox backup-dashboard__collapsible-checkbox restore-preview__blob-transfer-row restore-preview__blob-transfer-row--with-help-tip",
|
|
835
|
+
children: [
|
|
836
|
+
/*#__PURE__*/ _jsx("input", {
|
|
837
|
+
"aria-label": "Copy existing backup files into this storage when saving",
|
|
838
|
+
checked: transferBackupBlobs,
|
|
839
|
+
className: "checkbox-input__input",
|
|
840
|
+
id: transferOptId,
|
|
841
|
+
onChange: (e)=>setTransferBackupBlobs(e.target.checked),
|
|
842
|
+
type: "checkbox"
|
|
843
|
+
}),
|
|
844
|
+
/*#__PURE__*/ _jsx("label", {
|
|
845
|
+
className: "field-label",
|
|
846
|
+
htmlFor: transferOptId,
|
|
847
|
+
children: "Copy existing backup files into this storage when saving"
|
|
848
|
+
}),
|
|
849
|
+
/*#__PURE__*/ _jsx(SettingsHelpTip, {
|
|
850
|
+
multiline: true,
|
|
851
|
+
tip: TIP_TRANSFER_COPY
|
|
852
|
+
})
|
|
853
|
+
]
|
|
854
|
+
}),
|
|
855
|
+
transferBackupBlobs ? /*#__PURE__*/ _jsxs("div", {
|
|
856
|
+
className: "field-type checkbox backup-dashboard__collapsible-checkbox restore-preview__blob-transfer-row restore-preview__blob-transfer-row--with-help-tip",
|
|
857
|
+
children: [
|
|
858
|
+
/*#__PURE__*/ _jsx("input", {
|
|
859
|
+
"aria-label": "Delete from previous storage after successful copy",
|
|
860
|
+
checked: deleteBackupBlobsFromSource,
|
|
861
|
+
className: "checkbox-input__input",
|
|
862
|
+
id: deleteSourceOptId,
|
|
863
|
+
onChange: (e)=>setDeleteBackupBlobsFromSource(e.target.checked),
|
|
864
|
+
type: "checkbox"
|
|
865
|
+
}),
|
|
866
|
+
/*#__PURE__*/ _jsx("label", {
|
|
867
|
+
className: "field-label",
|
|
868
|
+
htmlFor: deleteSourceOptId,
|
|
869
|
+
children: "Delete from previous storage after successful copy"
|
|
870
|
+
}),
|
|
871
|
+
/*#__PURE__*/ _jsx(SettingsHelpTip, {
|
|
872
|
+
multiline: true,
|
|
873
|
+
tip: TIP_TRANSFER_DELETE
|
|
874
|
+
})
|
|
875
|
+
]
|
|
876
|
+
}) : null
|
|
877
|
+
]
|
|
878
|
+
}) : null
|
|
879
|
+
]
|
|
880
|
+
}),
|
|
881
|
+
/*#__PURE__*/ _jsx(CollectionBackupPreviewBody, {
|
|
882
|
+
embedded: true,
|
|
883
|
+
errorMessage: errorMessage,
|
|
884
|
+
includeAllCollections: backupAllCollections,
|
|
885
|
+
includeAllLabel: "Backup all collections",
|
|
886
|
+
includeMediaBlobs: includeMediaBlobs,
|
|
887
|
+
onRetry: loadPreview,
|
|
888
|
+
onToggleGroup: onToggleGroup,
|
|
889
|
+
onToggleIncludeAllCollections: onToggleBackupAllCollections,
|
|
890
|
+
onToggleIncludeMedia: ()=>setIncludeMediaBlobs((v)=>!v),
|
|
891
|
+
phase: phase,
|
|
892
|
+
preview: preview,
|
|
893
|
+
selected: selected
|
|
894
|
+
}),
|
|
895
|
+
saveError ? /*#__PURE__*/ _jsx("p", {
|
|
896
|
+
className: "restore-preview__error restore-preview__error--inline",
|
|
897
|
+
children: saveError
|
|
898
|
+
}) : null,
|
|
899
|
+
savePhase === 'saving' ? /*#__PURE__*/ _jsx("p", {
|
|
900
|
+
className: "restore-preview__status restore-preview__status--footer",
|
|
901
|
+
children: "Saving settings…"
|
|
902
|
+
}) : null,
|
|
903
|
+
transferSummary?.performed ? /*#__PURE__*/ _jsxs("p", {
|
|
904
|
+
className: "restore-preview__status restore-preview__status--footer",
|
|
905
|
+
children: [
|
|
906
|
+
"Migrated backups: ",
|
|
907
|
+
transferSummary.transferred,
|
|
908
|
+
"/",
|
|
909
|
+
transferSummary.total,
|
|
910
|
+
transferSummary.failed > 0 ? `, failed: ${transferSummary.failed}` : ''
|
|
911
|
+
]
|
|
912
|
+
}) : null
|
|
913
|
+
]
|
|
914
|
+
}),
|
|
915
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
916
|
+
className: "backup-confirm-dialog__actions",
|
|
917
|
+
children: [
|
|
918
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
919
|
+
buttonStyle: "primary",
|
|
920
|
+
disabled: phase !== 'ready' || savePhase === 'saving' || savePhase === 'transferring' || blockSaveForTokenCheck,
|
|
921
|
+
onClick: ()=>void save(),
|
|
922
|
+
size: "small",
|
|
923
|
+
children: savePhase === 'transferring' ? 'Transferring…' : savePhase === 'saving' ? 'Saving…' : savePhase === 'saved' ? 'Saved' : 'Save settings'
|
|
924
|
+
}),
|
|
925
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
926
|
+
buttonStyle: "secondary",
|
|
927
|
+
onClick: ()=>dialogRef.current?.close(),
|
|
928
|
+
size: "small",
|
|
929
|
+
children: "Cancel"
|
|
930
|
+
})
|
|
931
|
+
]
|
|
932
|
+
})
|
|
933
|
+
]
|
|
934
|
+
})
|
|
935
|
+
]
|
|
936
|
+
});
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
//# sourceMappingURL=BackupSettingsModal.client.js.map
|