@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.
Files changed (64) hide show
  1. package/dist-lib/components/Plan/Backups/Backups.module.scss.js +44 -42
  2. package/dist-lib/components/Plan/Backups/Backups.module.scss.js.map +1 -1
  3. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts +3 -2
  4. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts.map +1 -1
  5. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.js +29 -22
  6. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.js.map +1 -1
  7. package/dist-lib/components/Plan/PlanProgress/PlanProgress.d.ts +2 -1
  8. package/dist-lib/components/Plan/PlanProgress/PlanProgress.d.ts.map +1 -1
  9. package/dist-lib/components/Plan/PlanProgress/PlanProgress.js +22 -18
  10. package/dist-lib/components/Plan/PlanProgress/PlanProgress.js.map +1 -1
  11. package/dist-lib/components/Plan/Restores/Restores.js +5 -5
  12. package/dist-lib/components/Plan/Restores/Restores.js.map +1 -1
  13. package/dist-lib/components/Restore/RestoreFileSelector/RestoreFileSelector.js +73 -73
  14. package/dist-lib/components/Restore/RestoreFileSelector/RestoreFileSelector.js.map +1 -1
  15. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.d.ts.map +1 -1
  16. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js +50 -50
  17. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js.map +1 -1
  18. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js +8 -8
  19. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js.map +1 -1
  20. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js +34 -36
  21. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js.map +1 -1
  22. package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.d.ts +2 -1
  23. package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.d.ts.map +1 -1
  24. package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.js +62 -60
  25. package/dist-lib/components/Restore/RestoredFileBrowser/RestoredFileBrowser.js.map +1 -1
  26. package/dist-lib/components/common/LogViewer/LogViewer.d.ts.map +1 -1
  27. package/dist-lib/components/common/LogViewer/LogViewer.js +51 -47
  28. package/dist-lib/components/common/LogViewer/LogViewer.js.map +1 -1
  29. package/dist-lib/components/common/LogViewer/LogViewer.module.scss.js +24 -24
  30. package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
  31. package/dist-lib/hooks/usePlanSingleActions.js +49 -37
  32. package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
  33. package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
  34. package/dist-lib/routes/PlanSingle/PlanSingle.js +68 -67
  35. package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
  36. package/dist-lib/services/backups.d.ts.map +1 -1
  37. package/dist-lib/services/backups.js +35 -34
  38. package/dist-lib/services/backups.js.map +1 -1
  39. package/dist-lib/services/plans.d.ts +5 -2
  40. package/dist-lib/services/plans.d.ts.map +1 -1
  41. package/dist-lib/services/plans.js +45 -43
  42. package/dist-lib/services/plans.js.map +1 -1
  43. package/dist-lib/services/restores.d.ts.map +1 -1
  44. package/dist-lib/services/restores.js +56 -55
  45. package/dist-lib/services/restores.js.map +1 -1
  46. package/dist-lib/services.js +19 -19
  47. package/dist-lib/styles/core-frontend.css +1 -1
  48. package/package.json +1 -1
  49. package/src/components/Plan/Backups/Backups.module.scss +2 -1
  50. package/src/components/Plan/PlanPendingBackup/PlanPendingBackup.tsx +27 -18
  51. package/src/components/Plan/PlanProgress/PlanProgress.tsx +13 -2
  52. package/src/components/Plan/Restores/Restores.tsx +1 -1
  53. package/src/components/Restore/RestoreFileSelector/RestoreFileSelector.tsx +4 -4
  54. package/src/components/Restore/RestoreWizard/RestoreConfirmStep.tsx +26 -19
  55. package/src/components/Restore/RestoreWizard/RestorePreviewStep.tsx +1 -1
  56. package/src/components/Restore/RestoreWizard/RestoreWizard.module.scss +2 -2
  57. package/src/components/Restore/RestoredFileBrowser/RestoredFileBrowser.tsx +18 -14
  58. package/src/components/common/LogViewer/LogViewer.module.scss +1 -1
  59. package/src/components/common/LogViewer/LogViewer.tsx +18 -10
  60. package/src/hooks/usePlanSingleActions.tsx +21 -8
  61. package/src/routes/PlanSingle/PlanSingle.tsx +2 -0
  62. package/src/services/backups.ts +12 -4
  63. package/src/services/plans.ts +4 -3
  64. package/src/services/restores.ts +12 -4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutonhq/core-frontend",
3
3
  "description": "Pluton Core Frontend Library",
4
- "version": "0.1.6",
4
+ "version": "0.1.7",
5
5
  "author": "Plutonhq",
6
6
  "license": "Apache-2.0",
7
7
  "publishConfig": {
@@ -47,7 +47,8 @@
47
47
  cursor: pointer;
48
48
  transition: all 0.12s linear;
49
49
  }
50
- .backupTitle {
50
+ .backupTitle,
51
+ .restoreTitle {
51
52
  cursor: pointer;
52
53
 
53
54
  &:hover {
@@ -1,37 +1,46 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useSearchParams } from 'react-router';
3
- import { useCheckActiveBackups } from '../../../services/plans';
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
- onPendingBackupDetect: () => void;
9
+ type?: 'backup' | 'restore';
10
+ onPendingDetect: () => void;
10
11
  }
11
12
 
12
- const PlanPendingBackup = ({ planId, onPendingBackupDetect }: PlanPendingBackup) => {
13
+ const PlanPendingBackup = ({ planId, type = 'backup', onPendingDetect }: PlanPendingBackup) => {
13
14
  const [, setSearchParams] = useSearchParams();
14
- const checkActiveBackupsMutation = useCheckActiveBackups();
15
+ const checkActivesMutation = useCheckActiveBackupsOrRestore();
15
16
 
16
17
  useEffect(() => {
17
18
  const interval = window.setInterval(() => {
18
- checkActiveBackupsMutation.mutate(planId, {
19
- onSuccess: (data) => {
20
- console.log('[isBackupPending] data :', data);
21
- if (data.result) {
22
- window.clearInterval(interval);
23
- setSearchParams((params) => {
24
- params.delete('pendingbackup');
25
- return params;
26
- });
27
- onPendingBackupDetect();
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 Backup...</div>
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} onPendingBackupDetect={() => refetchPlan()} />
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.backupTitle} onClick={() => !isSync && setShowRestoreEvents(id)}>
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
- selectedBytes += file.size;
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 - 300} itemCount={directChildren.length} itemSize={ITEM_HEIGHT} width="100%">
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
- backupId,
26
- planId,
27
- overwrite: settings.overwrite,
28
- target: settings.type === 'custom' ? settings.path : '',
29
- includes: settings.includes,
30
- excludes: settings.excludes,
31
- deleteOption: settings.delete,
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
- {restoreMutation.isSuccess ? 'Close' : 'Cancel'}
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}>
@@ -136,8 +136,8 @@
136
136
  .fileManagerBtn {
137
137
  position: absolute;
138
138
  right: 0;
139
- top: 22px;
140
- padding: 10px;
139
+ top: 0;
140
+ padding: 8px;
141
141
  cursor: pointer;
142
142
  color: var(--icon-color);
143
143
  &:hover {
@@ -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
- Object.keys(fileSystem).forEach((path) => {
73
- const separator = getPathSeparator(path);
74
- const parts = splitPath(path);
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
- const dirsArray = Array.from(dirs);
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
- return dirsArray;
91
- }, [fileSystem]);
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 it's content</div>
324
+ <div className={classes.fileListEmpty}>Select a folder from the left to browse its content</div>
321
325
  )}
322
326
  </div>
323
327
  </div>
@@ -68,7 +68,7 @@
68
68
  }
69
69
  }
70
70
  .logs {
71
- height: calc(100vh - 290px);
71
+ height: calc(100vh - 300px);
72
72
  overflow: auto;
73
73
 
74
74
  .log {
@@ -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[]>(allTaskTypes);
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
- const theLogs = logs.filter((log) => {
34
- const matchesSearch = log.msg?.toLowerCase().includes(search.toLowerCase());
35
- const matchesBackupId = log.backupId?.toLowerCase().includes(search.toLowerCase());
36
- const logType = getLogLevelName(log.level);
37
- const matchesFilter = filters.includes(logType);
38
- const matchesTaskType = taskTypes.includes(log.module);
39
- return (matchesSearch || matchesBackupId) && matchesFilter && matchesTaskType;
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.promise(performBackupMutation.mutateAsync(plan.id), {
98
- pending: `Starting ${isSync ? 'Sync' : 'Backup'}...`,
99
- success: `${isSync ? 'Sync' : 'Backup'} initiated successfully! 🚀`,
100
- error: {
101
- render({ data }: any) {
102
- return `${isSync ? 'Sync' : 'Backup'} failed to start. ${data?.message || 'Unknown Error.'}`;
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}
@@ -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 (planId) {
165
- console.log('Invalidate Plan and Reload It :', planId);
166
- queryClient.invalidateQueries({ queryKey: ['plan', planId] });
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
 
@@ -370,8 +370,9 @@ export function useUnlockPlan() {
370
370
  }
371
371
 
372
372
  // Get Backup Progress
373
- export async function checkActiveBackups(planId: string) {
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 useCheckActiveBackups() {
388
+ export function useCheckActiveBackupsOrRestore() {
388
389
  return useMutation({
389
390
  // queryKey: ['planActiveBackups-' + planId],
390
- mutationFn: checkActiveBackups,
391
+ mutationFn: ({ planId, type }: { planId: string; type: 'backup' | 'restore' }) => checkActiveBackupsOrRestore(planId, type),
391
392
  // refetchOnMount: true,
392
393
  });
393
394
  }
@@ -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 (planId) {
243
- console.log('Invalidate Plan and Reload It :', planId);
244
- queryClient.invalidateQueries({ queryKey: ['plan', planId] });
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