@plutonhq/core-frontend 0.1.6 → 0.1.7
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/dist-lib/components/Plan/Backups/Backups.module.scss.js +44 -42
- package/dist-lib/components/Plan/Backups/Backups.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts +3 -2
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.js +29 -22
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.js.map +1 -1
- package/dist-lib/components/Plan/PlanProgress/PlanProgress.d.ts +2 -1
- package/dist-lib/components/Plan/PlanProgress/PlanProgress.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanProgress/PlanProgress.js +22 -18
- package/dist-lib/components/Plan/PlanProgress/PlanProgress.js.map +1 -1
- package/dist-lib/components/Plan/Restores/Restores.js +5 -5
- package/dist-lib/components/Plan/Restores/Restores.js.map +1 -1
- package/dist-lib/components/Restore/RestoreFileSelector/RestoreFileSelector.js +73 -73
- package/dist-lib/components/Restore/RestoreFileSelector/RestoreFileSelector.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js +50 -50
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js +8 -8
- package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js +34 -36
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js.map +1 -1
- package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.d.ts +2 -1
- package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.js +62 -60
- package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.js.map +1 -1
- package/dist-lib/components/common/LogViewer/LogViewer.d.ts.map +1 -1
- package/dist-lib/components/common/LogViewer/LogViewer.js +51 -47
- package/dist-lib/components/common/LogViewer/LogViewer.js.map +1 -1
- package/dist-lib/components/common/LogViewer/LogViewer.module.scss.js +24 -24
- package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
- package/dist-lib/hooks/usePlanSingleActions.js +49 -37
- package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.js +68 -67
- package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
- package/dist-lib/services/backups.d.ts.map +1 -1
- package/dist-lib/services/backups.js +35 -34
- package/dist-lib/services/backups.js.map +1 -1
- package/dist-lib/services/plans.d.ts +5 -2
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +45 -43
- package/dist-lib/services/plans.js.map +1 -1
- package/dist-lib/services/restores.d.ts.map +1 -1
- package/dist-lib/services/restores.js +56 -55
- package/dist-lib/services/restores.js.map +1 -1
- package/dist-lib/services.js +19 -19
- package/dist-lib/styles/core-frontend.css +1 -1
- package/package.json +1 -1
- package/src/components/Plan/Backups/Backups.module.scss +2 -1
- package/src/components/Plan/PlanPendingBackup/PlanPendingBackup.tsx +27 -18
- package/src/components/Plan/PlanProgress/PlanProgress.tsx +13 -2
- package/src/components/Plan/Restores/Restores.tsx +1 -1
- package/src/components/Restore/RestoreFileSelector/RestoreFileSelector.tsx +4 -4
- package/src/components/Restore/RestoreWizard/RestoreConfirmStep.tsx +26 -19
- package/src/components/Restore/RestoreWizard/RestorePreviewStep.tsx +1 -1
- package/src/components/Restore/RestoreWizard/RestoreWizard.module.scss +2 -2
- package/src/components/Restore/RestoredFileBrowser/RestoredFileBrowser.tsx +18 -14
- package/src/components/common/LogViewer/LogViewer.module.scss +1 -1
- package/src/components/common/LogViewer/LogViewer.tsx +18 -10
- package/src/hooks/usePlanSingleActions.tsx +21 -8
- package/src/routes/PlanSingle/PlanSingle.tsx +2 -0
- package/src/services/backups.ts +12 -4
- package/src/services/plans.ts +4 -3
- package/src/services/restores.ts +12 -4
package/package.json
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useSearchParams } from 'react-router';
|
|
3
|
-
import {
|
|
3
|
+
import { useCheckActiveBackupsOrRestore } from '../../../services/plans';
|
|
4
4
|
import classes from './PlanPendingBackup.module.scss';
|
|
5
5
|
import Icon from '../../common/Icon/Icon';
|
|
6
6
|
|
|
7
7
|
interface PlanPendingBackup {
|
|
8
8
|
planId: string;
|
|
9
|
-
|
|
9
|
+
type?: 'backup' | 'restore';
|
|
10
|
+
onPendingDetect: () => void;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const PlanPendingBackup = ({ planId,
|
|
13
|
+
const PlanPendingBackup = ({ planId, type = 'backup', onPendingDetect }: PlanPendingBackup) => {
|
|
13
14
|
const [, setSearchParams] = useSearchParams();
|
|
14
|
-
const
|
|
15
|
+
const checkActivesMutation = useCheckActiveBackupsOrRestore();
|
|
15
16
|
|
|
16
17
|
useEffect(() => {
|
|
17
18
|
const interval = window.setInterval(() => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
checkActivesMutation.mutate(
|
|
20
|
+
{ planId, type },
|
|
21
|
+
{
|
|
22
|
+
onSuccess: (data) => {
|
|
23
|
+
console.log('[isBackupPending] data :', data);
|
|
24
|
+
if (data.result) {
|
|
25
|
+
window.clearInterval(interval);
|
|
26
|
+
setSearchParams((params) => {
|
|
27
|
+
if (type === 'restore') {
|
|
28
|
+
params.delete('pendingrestore');
|
|
29
|
+
} else {
|
|
30
|
+
params.delete('pendingbackup');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return params;
|
|
34
|
+
});
|
|
35
|
+
onPendingDetect();
|
|
36
|
+
}
|
|
37
|
+
},
|
|
29
38
|
},
|
|
30
|
-
|
|
39
|
+
);
|
|
31
40
|
}, 1000);
|
|
32
41
|
|
|
33
42
|
return () => window.clearInterval(interval);
|
|
34
|
-
}, [planId]);
|
|
43
|
+
}, [planId, type]);
|
|
35
44
|
|
|
36
45
|
return (
|
|
37
46
|
<div className={classes.backup}>
|
|
@@ -39,7 +48,7 @@ const PlanPendingBackup = ({ planId, onPendingBackupDetect }: PlanPendingBackup)
|
|
|
39
48
|
<Icon type="loading" size={24} />
|
|
40
49
|
</div>
|
|
41
50
|
<div className={classes.backupLeft}>
|
|
42
|
-
<div className={classes.backupId}>Starting
|
|
51
|
+
<div className={classes.backupId}>Starting {type}...</div>
|
|
43
52
|
<div className={classes.backupStart}>
|
|
44
53
|
<div>
|
|
45
54
|
<Icon type="clock" size={12} /> Starting in a few seconds
|
|
@@ -8,12 +8,13 @@ import classes from './PlanProgress.module.scss';
|
|
|
8
8
|
interface PlanProgressProps {
|
|
9
9
|
plan: Plan;
|
|
10
10
|
isBackupPending: boolean;
|
|
11
|
+
isRestorePending?: boolean;
|
|
11
12
|
activeBackups: Backup[];
|
|
12
13
|
activeRestores: RestoreSlim[];
|
|
13
14
|
refetchPlan: () => void;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const PlanProgress = ({ plan, isBackupPending, activeBackups, activeRestores, refetchPlan }: PlanProgressProps) => {
|
|
17
|
+
const PlanProgress = ({ plan, isBackupPending, isRestorePending, activeBackups, activeRestores, refetchPlan }: PlanProgressProps) => {
|
|
17
18
|
return (
|
|
18
19
|
<div>
|
|
19
20
|
{isBackupPending && (
|
|
@@ -22,7 +23,7 @@ const PlanProgress = ({ plan, isBackupPending, activeBackups, activeRestores, re
|
|
|
22
23
|
<h3>Backup in Progress</h3>
|
|
23
24
|
</div>
|
|
24
25
|
<div className={classes.activeBackupsTable}>
|
|
25
|
-
<PlanPendingBackup planId={plan.id}
|
|
26
|
+
<PlanPendingBackup planId={plan.id} onPendingDetect={() => refetchPlan()} />
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
29
|
)}
|
|
@@ -49,6 +50,16 @@ const PlanProgress = ({ plan, isBackupPending, activeBackups, activeRestores, re
|
|
|
49
50
|
</div>
|
|
50
51
|
)}
|
|
51
52
|
|
|
53
|
+
{isRestorePending && (
|
|
54
|
+
<div className={classes.activeBackups}>
|
|
55
|
+
<div className={classes.backupsHeader}>
|
|
56
|
+
<h3>Restore in Progress</h3>
|
|
57
|
+
</div>
|
|
58
|
+
<div className={classes.activeBackupsTable}>
|
|
59
|
+
<PlanPendingBackup planId={plan.id} type="restore" onPendingDetect={() => refetchPlan()} />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
52
63
|
{activeRestores.length > 0 && (
|
|
53
64
|
<div className={classes.activeBackups}>
|
|
54
65
|
<div className={classes.backupsHeader}>
|
|
@@ -69,7 +69,7 @@ const Restores = ({
|
|
|
69
69
|
key={id}
|
|
70
70
|
className={`${classes.backupsTableRow} ${showSnapOptions && showSnapOptions === id ? classes.backupsTableRowActive : ''}`}
|
|
71
71
|
>
|
|
72
|
-
<div className={classes.
|
|
72
|
+
<div className={classes.restoreTitle} onClick={() => !isSync && setShowRestoreEvents(id)}>
|
|
73
73
|
<Icon type={isSync ? 'sync' : 'box'} size={14} /> {isSync ? 'sync' : 'backup'}-{backupId}
|
|
74
74
|
</div>
|
|
75
75
|
<div
|
|
@@ -165,12 +165,12 @@ const RestoreFileSelector = ({ selected, files, isLoading, errorFetching, showCh
|
|
|
165
165
|
|
|
166
166
|
const restorableFiles = fileSelectCondition ? files.filter((file) => fileSelectCondition(file)) : files;
|
|
167
167
|
|
|
168
|
-
console.log('restorableFiles :', restorableFiles);
|
|
169
|
-
|
|
170
168
|
restorableFiles.forEach((file) => {
|
|
171
169
|
if (isPathSelected(file.path)) {
|
|
172
170
|
selectedFilesCount++;
|
|
173
|
-
|
|
171
|
+
if (!file.isDirectory) {
|
|
172
|
+
selectedBytes += file.size;
|
|
173
|
+
}
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
176
|
|
|
@@ -500,7 +500,7 @@ const RestoreFileSelector = ({ selected, files, isLoading, errorFetching, showCh
|
|
|
500
500
|
</div>
|
|
501
501
|
</div>
|
|
502
502
|
{selectedFolder ? (
|
|
503
|
-
<List height={window.innerHeight -
|
|
503
|
+
<List height={window.innerHeight - 370} itemCount={directChildren.length} itemSize={ITEM_HEIGHT} width="100%">
|
|
504
504
|
{Row}
|
|
505
505
|
</List>
|
|
506
506
|
) : (
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { toast } from 'react-toastify';
|
|
2
|
+
import { useNavigate } from 'react-router';
|
|
1
3
|
import Icon from '../../common/Icon/Icon';
|
|
2
4
|
import { useRestoreBackup } from '../../../services/restores';
|
|
3
5
|
import classes from './RestoreWizard.module.scss';
|
|
@@ -16,20 +18,34 @@ interface RestoreConfirmStepProps {
|
|
|
16
18
|
|
|
17
19
|
const RestoreConfirmStep = ({ backupId, planId, settings, stats, snapshotsStats, method, goBack, close }: RestoreConfirmStepProps) => {
|
|
18
20
|
const restoreMutation = useRestoreBackup();
|
|
21
|
+
const navigate = useNavigate();
|
|
19
22
|
const restoreStats = stats;
|
|
20
23
|
const isSync = method === 'sync';
|
|
21
24
|
|
|
22
25
|
const restoreBackup = () => {
|
|
23
26
|
console.log('restore :', backupId);
|
|
24
|
-
restoreMutation.mutate(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
restoreMutation.mutate(
|
|
28
|
+
{
|
|
29
|
+
backupId,
|
|
30
|
+
planId,
|
|
31
|
+
overwrite: settings.overwrite,
|
|
32
|
+
target: settings.type === 'custom' ? settings.path : '',
|
|
33
|
+
includes: settings.includes,
|
|
34
|
+
excludes: settings.excludes,
|
|
35
|
+
deleteOption: settings.delete,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
onSuccess: (data: any, variables) => {
|
|
39
|
+
console.log('Success :', data);
|
|
40
|
+
toast.success(`Restore Started`, { autoClose: 5000 });
|
|
41
|
+
const targetPlanId = variables?.planId;
|
|
42
|
+
if (targetPlanId) {
|
|
43
|
+
navigate(`/plan/${targetPlanId}?pendingrestore=1`);
|
|
44
|
+
}
|
|
45
|
+
close();
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
);
|
|
33
49
|
};
|
|
34
50
|
|
|
35
51
|
return (
|
|
@@ -69,15 +85,6 @@ const RestoreConfirmStep = ({ backupId, planId, settings, stats, snapshotsStats,
|
|
|
69
85
|
</button>
|
|
70
86
|
</div>
|
|
71
87
|
)}
|
|
72
|
-
{restoreMutation.isSuccess && (
|
|
73
|
-
<p className={classes.restoreSuccessMsg}>
|
|
74
|
-
<Icon type="check-circle-filled" size={14} color="lightseagreen" /> Restore Process Started.{' '}
|
|
75
|
-
<button onClick={() => location.reload()}>
|
|
76
|
-
<Icon type="reload" size={12} /> Reload
|
|
77
|
-
</button>{' '}
|
|
78
|
-
the page to view the restoration progress.
|
|
79
|
-
</p>
|
|
80
|
-
)}
|
|
81
88
|
{restoreMutation.isError && (
|
|
82
89
|
<div className={classes.restoreError}>
|
|
83
90
|
<Icon type="error" size={14} color="red" /> {restoreMutation.error?.message || 'Failed to Generate Preview'}
|
|
@@ -94,7 +101,7 @@ const RestoreConfirmStep = ({ backupId, planId, settings, stats, snapshotsStats,
|
|
|
94
101
|
</div>
|
|
95
102
|
<div className={classes.footerRight}>
|
|
96
103
|
<button onClick={() => close()} disabled={restoreMutation.isPending}>
|
|
97
|
-
|
|
104
|
+
Cancel
|
|
98
105
|
</button>
|
|
99
106
|
</div>
|
|
100
107
|
</div>
|
|
@@ -96,7 +96,7 @@ const RestorePreviewStep = ({ backupId, planId, settings, preview, nextLabel, go
|
|
|
96
96
|
</button>
|
|
97
97
|
</div>
|
|
98
98
|
)}
|
|
99
|
-
{restoreStats && <RestoredFileBrowser files={restoredFiles} stats={restoreStats} />}
|
|
99
|
+
{restoreStats && <RestoredFileBrowser files={restoredFiles} stats={restoreStats} isPreview={true} />}
|
|
100
100
|
</div>
|
|
101
101
|
<div className={classes.footer}>
|
|
102
102
|
<div className={classes.footerLeft}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react';
|
|
1
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
2
2
|
import { FixedSizeList as List } from 'react-window';
|
|
3
3
|
import Icon from '../../common/Icon/Icon';
|
|
4
4
|
import classes from './RestoredFileBrowser.module.scss';
|
|
@@ -10,12 +10,13 @@ import FileIcon from '../../common/FileIcon/FileIcon';
|
|
|
10
10
|
interface RestoredFileBrowserProps {
|
|
11
11
|
files: RestoredFileItem[];
|
|
12
12
|
stats?: RestoredItemsStats;
|
|
13
|
+
isPreview?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const isMobileDevice = isMobile();
|
|
16
17
|
const ITEM_HEIGHT = isMobileDevice ? 65 : 45;
|
|
17
18
|
|
|
18
|
-
const RestoredFileBrowser = ({ files, stats }: RestoredFileBrowserProps) => {
|
|
19
|
+
const RestoredFileBrowser = ({ files, stats, isPreview = false }: RestoredFileBrowserProps) => {
|
|
19
20
|
const [selectedFolder, setSelectedFolder] = useState<string>('');
|
|
20
21
|
const [search, setSearch] = useState('');
|
|
21
22
|
const [filters, setFilters] = useState({
|
|
@@ -69,9 +70,11 @@ const RestoredFileBrowser = ({ files, stats }: RestoredFileBrowserProps) => {
|
|
|
69
70
|
|
|
70
71
|
const directories = useMemo(() => {
|
|
71
72
|
const dirs = new Set<string>();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
73
|
+
// Derive directories from all files, not filtered fileSystem, to keep tree stable during search
|
|
74
|
+
files.forEach((file) => {
|
|
75
|
+
const dirPath = getParentPath(file.path);
|
|
76
|
+
const separator = getPathSeparator(dirPath);
|
|
77
|
+
const parts = splitPath(dirPath);
|
|
75
78
|
let currentPath = '';
|
|
76
79
|
|
|
77
80
|
parts.forEach((part) => {
|
|
@@ -81,14 +84,15 @@ const RestoredFileBrowser = ({ files, stats }: RestoredFileBrowserProps) => {
|
|
|
81
84
|
}
|
|
82
85
|
});
|
|
83
86
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Set the first directory as selected by default
|
|
87
|
-
console.log('SelectedFolder :', dirsArray[0]);
|
|
88
|
-
setSelectedFolder(dirsArray[0] || '');
|
|
87
|
+
return Array.from(dirs);
|
|
88
|
+
}, [files]);
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Set initial selected folder when directories change
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (directories.length > 0 && selectedFolder === '') {
|
|
93
|
+
setSelectedFolder(directories[0]);
|
|
94
|
+
}
|
|
95
|
+
}, [directories, selectedFolder]);
|
|
92
96
|
|
|
93
97
|
const hasSubdirectories = (dir: string) => {
|
|
94
98
|
const separator = getPathSeparator(dir);
|
|
@@ -308,7 +312,7 @@ const RestoredFileBrowser = ({ files, stats }: RestoredFileBrowserProps) => {
|
|
|
308
312
|
</div>
|
|
309
313
|
{selectedFolder && totalItems > 0 ? (
|
|
310
314
|
<List
|
|
311
|
-
height={window.innerHeight - 250}
|
|
315
|
+
height={window.innerHeight - (isPreview ? 370 : 250)}
|
|
312
316
|
itemCount={totalItems}
|
|
313
317
|
itemSize={ITEM_HEIGHT}
|
|
314
318
|
width="100%"
|
|
@@ -317,7 +321,7 @@ const RestoredFileBrowser = ({ files, stats }: RestoredFileBrowserProps) => {
|
|
|
317
321
|
{FileRow}
|
|
318
322
|
</List>
|
|
319
323
|
) : (
|
|
320
|
-
<div className={classes.fileListEmpty}>Select a folder from the left to browse
|
|
324
|
+
<div className={classes.fileListEmpty}>Select a folder from the left to browse its content</div>
|
|
321
325
|
)}
|
|
322
326
|
</div>
|
|
323
327
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
2
|
import Icon from '../Icon/Icon';
|
|
3
3
|
import classes from './LogViewer.module.scss';
|
|
4
4
|
import { useGetDownloadLogs } from '../../../services/plans';
|
|
@@ -25,19 +25,27 @@ const LogViewer = ({ type = '', planMethod = 'backup', logs = [], planId, settin
|
|
|
25
25
|
const allTaskTypes = [...new Set(logs.map((log) => log.module))];
|
|
26
26
|
const [search, setSearch] = useState('');
|
|
27
27
|
const [filters, setFilters] = useState<string[]>(['info', 'error', 'warn']);
|
|
28
|
-
const [taskTypes, setTaskTypes] = useState<string[]>(
|
|
28
|
+
const [taskTypes, setTaskTypes] = useState<string[]>([]);
|
|
29
29
|
const [currentPage, setCurrentPage] = useState(1);
|
|
30
30
|
const downloadPlanLogsMutation = useGetDownloadLogs();
|
|
31
31
|
const downloadAppLogsMutation = useGetDownloadAppLogs();
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (allTaskTypes.length > 0 && taskTypes.length === 0) {
|
|
35
|
+
setTaskTypes(allTaskTypes);
|
|
36
|
+
}
|
|
37
|
+
}, [allTaskTypes.length]);
|
|
38
|
+
|
|
39
|
+
const theLogs = logs
|
|
40
|
+
.filter((log) => {
|
|
41
|
+
const matchesSearch = log.msg?.toLowerCase().includes(search.toLowerCase());
|
|
42
|
+
const matchesBackupId = log.backupId?.toLowerCase().includes(search.toLowerCase());
|
|
43
|
+
const logType = getLogLevelName(log.level);
|
|
44
|
+
const matchesFilter = filters.includes(logType);
|
|
45
|
+
const matchesTaskType = taskTypes.includes(log.module);
|
|
46
|
+
return (matchesSearch || matchesBackupId) && matchesFilter && matchesTaskType;
|
|
47
|
+
})
|
|
48
|
+
.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
|
|
41
49
|
|
|
42
50
|
const showPagination = theLogs.length > PAGINATION_THRESHOLD;
|
|
43
51
|
const totalPages = Math.ceil(theLogs.length / ITEMS_PER_PAGE);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { useParams } from 'react-router';
|
|
2
|
+
import { useNavigate, useParams } from 'react-router';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
import { useGetPlan, usePausePlan, usePerformBackup, useResumePlan } from '../services/plans';
|
|
5
5
|
import { Plan } from '../@types/plans';
|
|
@@ -37,6 +37,7 @@ export const usePlanSingleActions = (): {
|
|
|
37
37
|
const [showLogsModal, setShowLogsModal] = useState(false);
|
|
38
38
|
|
|
39
39
|
const { id } = useParams();
|
|
40
|
+
const navigate = useNavigate();
|
|
40
41
|
|
|
41
42
|
const performBackupMutation = usePerformBackup();
|
|
42
43
|
const pauseMutation = usePausePlan();
|
|
@@ -94,13 +95,25 @@ export const usePlanSingleActions = (): {
|
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
toast.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const toastId = toast.loading(`Starting ${isSync ? 'Sync' : 'Backup'}...`);
|
|
99
|
+
|
|
100
|
+
performBackupMutation.mutate(plan.id, {
|
|
101
|
+
onSuccess: () => {
|
|
102
|
+
toast.update(toastId, {
|
|
103
|
+
render: `${isSync ? 'Sync' : 'Backup'} initiated successfully! 🚀`,
|
|
104
|
+
type: 'success',
|
|
105
|
+
isLoading: false,
|
|
106
|
+
autoClose: 3000,
|
|
107
|
+
});
|
|
108
|
+
navigate(`/plan/${plan.id}?pendingbackup=1`);
|
|
109
|
+
},
|
|
110
|
+
onError: (error: any) => {
|
|
111
|
+
toast.update(toastId, {
|
|
112
|
+
render: `${isSync ? 'Sync' : 'Backup'} failed to start. ${error?.message || 'Unknown Error.'}`,
|
|
113
|
+
type: 'error',
|
|
114
|
+
isLoading: false,
|
|
115
|
+
autoClose: false,
|
|
116
|
+
});
|
|
104
117
|
},
|
|
105
118
|
});
|
|
106
119
|
};
|
|
@@ -23,6 +23,7 @@ const PlanSingle = () => {
|
|
|
23
23
|
|
|
24
24
|
const [searchParams] = useSearchParams();
|
|
25
25
|
const isBackupPending = searchParams.get('pendingbackup') === '1';
|
|
26
|
+
const isRestorePending = searchParams.get('pendingrestore') === '1';
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
showDeleteModal,
|
|
@@ -154,6 +155,7 @@ const PlanSingle = () => {
|
|
|
154
155
|
<PlanProgress
|
|
155
156
|
plan={plan}
|
|
156
157
|
isBackupPending={isBackupPending}
|
|
158
|
+
isRestorePending={isRestorePending}
|
|
157
159
|
activeBackups={activeBackups}
|
|
158
160
|
activeRestores={activeRestores}
|
|
159
161
|
refetchPlan={refetchPlan}
|
package/src/services/backups.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
2
2
|
import { toast } from 'react-toastify';
|
|
3
3
|
import { API_URL } from '../utils/constants';
|
|
4
4
|
|
|
5
|
+
const notifiedBackupProgress = new Set<string>();
|
|
6
|
+
|
|
5
7
|
// Generate Download
|
|
6
8
|
export async function generateBackupDownload({ backupId }: { backupId: string; planId: string }) {
|
|
7
9
|
// const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
@@ -153,6 +155,9 @@ export function useGetBackupProgress(payload: { id: string; sourceId: string; so
|
|
|
153
155
|
refetchOnMount: true,
|
|
154
156
|
retry: false,
|
|
155
157
|
refetchInterval(query) {
|
|
158
|
+
// Only refetch if the browser tab is active
|
|
159
|
+
// if (document.hidden) return false;
|
|
160
|
+
|
|
156
161
|
// console.log('query :', query.state?.data);
|
|
157
162
|
const progressData = query.state?.data;
|
|
158
163
|
|
|
@@ -161,11 +166,14 @@ export function useGetBackupProgress(payload: { id: string; sourceId: string; so
|
|
|
161
166
|
|
|
162
167
|
if (isFinished) {
|
|
163
168
|
const planId = progressData?.planId || payload.planId;
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
if (!notifiedBackupProgress.has(payload.id)) {
|
|
170
|
+
notifiedBackupProgress.add(payload.id);
|
|
171
|
+
if (planId) {
|
|
172
|
+
console.log('Invalidate Plan and Reload It :', planId);
|
|
173
|
+
queryClient.invalidateQueries({ queryKey: ['plan', planId] });
|
|
174
|
+
}
|
|
175
|
+
toast.success('Process Complete!');
|
|
167
176
|
}
|
|
168
|
-
toast.success('Process Complete!');
|
|
169
177
|
return false;
|
|
170
178
|
}
|
|
171
179
|
|
package/src/services/plans.ts
CHANGED
|
@@ -370,8 +370,9 @@ export function useUnlockPlan() {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
// Get Backup Progress
|
|
373
|
-
export async function
|
|
373
|
+
export async function checkActiveBackupsOrRestore(planId: string, type: 'backup' | 'restore' = 'backup') {
|
|
374
374
|
const url = new URL(`${API_URL}/plans/${planId}/checkactive`);
|
|
375
|
+
url.searchParams.append('type', type);
|
|
375
376
|
|
|
376
377
|
const res = await fetch(url.toString(), {
|
|
377
378
|
method: 'GET',
|
|
@@ -384,10 +385,10 @@ export async function checkActiveBackups(planId: string) {
|
|
|
384
385
|
return data;
|
|
385
386
|
}
|
|
386
387
|
|
|
387
|
-
export function
|
|
388
|
+
export function useCheckActiveBackupsOrRestore() {
|
|
388
389
|
return useMutation({
|
|
389
390
|
// queryKey: ['planActiveBackups-' + planId],
|
|
390
|
-
mutationFn:
|
|
391
|
+
mutationFn: ({ planId, type }: { planId: string; type: 'backup' | 'restore' }) => checkActiveBackupsOrRestore(planId, type),
|
|
391
392
|
// refetchOnMount: true,
|
|
392
393
|
});
|
|
393
394
|
}
|
package/src/services/restores.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
|
2
2
|
import { toast } from 'react-toastify';
|
|
3
3
|
import { API_URL } from '../utils/constants';
|
|
4
4
|
|
|
5
|
+
const notifiedRestoreProgress = new Set<string>();
|
|
6
|
+
|
|
5
7
|
// Get All Restores
|
|
6
8
|
export async function getAllRestores() {
|
|
7
9
|
const url = new URL(`${API_URL}/restores`);
|
|
@@ -232,6 +234,9 @@ export function useGetRestoreProgress(payload: { id: string; sourceId: string; s
|
|
|
232
234
|
refetchOnMount: true,
|
|
233
235
|
retry: false,
|
|
234
236
|
refetchInterval(query) {
|
|
237
|
+
// Only refetch if the browser tab is active
|
|
238
|
+
// if (document.hidden) return false;
|
|
239
|
+
|
|
235
240
|
const progressData = query.state?.data;
|
|
236
241
|
|
|
237
242
|
// Check if backup is finished by looking for a "finished" phase event
|
|
@@ -239,11 +244,14 @@ export function useGetRestoreProgress(payload: { id: string; sourceId: string; s
|
|
|
239
244
|
|
|
240
245
|
if (isFinished) {
|
|
241
246
|
const planId = progressData?.planId || payload.planId;
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
if (!notifiedRestoreProgress.has(payload.id)) {
|
|
248
|
+
notifiedRestoreProgress.add(payload.id);
|
|
249
|
+
if (planId) {
|
|
250
|
+
console.log('Invalidate Plan and Reload It :', planId);
|
|
251
|
+
queryClient.invalidateQueries({ queryKey: ['plan', planId] });
|
|
252
|
+
}
|
|
253
|
+
toast.success('Restoration Complete!');
|
|
245
254
|
}
|
|
246
|
-
toast.success('Restoration Complete!');
|
|
247
255
|
return false;
|
|
248
256
|
}
|
|
249
257
|
|