@plutonhq/core-frontend 0.1.13 → 0.1.15

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 (217) hide show
  1. package/dist-lib/@types/backups.d.ts +26 -0
  2. package/dist-lib/@types/backups.d.ts.map +1 -1
  3. package/dist-lib/@types/devices.d.ts +7 -0
  4. package/dist-lib/@types/devices.d.ts.map +1 -1
  5. package/dist-lib/@types/plans.d.ts +21 -1
  6. package/dist-lib/@types/plans.d.ts.map +1 -1
  7. package/dist-lib/@types/restores.d.ts +2 -0
  8. package/dist-lib/@types/restores.d.ts.map +1 -1
  9. package/dist-lib/components/Device/DeviceBackups/DeviceBackups.d.ts +3 -2
  10. package/dist-lib/components/Device/DeviceBackups/DeviceBackups.d.ts.map +1 -1
  11. package/dist-lib/components/Device/DeviceBackups/DeviceBackups.js +73 -85
  12. package/dist-lib/components/Device/DeviceBackups/DeviceBackups.js.map +1 -1
  13. package/dist-lib/components/Plan/BackupEvents/BackupEvents.d.ts.map +1 -1
  14. package/dist-lib/components/Plan/BackupEvents/BackupEvents.js +88 -50
  15. package/dist-lib/components/Plan/BackupEvents/BackupEvents.js.map +1 -1
  16. package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js +70 -38
  17. package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js.map +1 -1
  18. package/dist-lib/components/Plan/BackupProgress/BackupProgress.d.ts.map +1 -1
  19. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +166 -123
  20. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
  21. package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js +64 -30
  22. package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js.map +1 -1
  23. package/dist-lib/components/Plan/Backups/Backups.d.ts +8 -1
  24. package/dist-lib/components/Plan/Backups/Backups.d.ts.map +1 -1
  25. package/dist-lib/components/Plan/Backups/Backups.js +154 -125
  26. package/dist-lib/components/Plan/Backups/Backups.js.map +1 -1
  27. package/dist-lib/components/Plan/EditPlan/EditPlan.d.ts.map +1 -1
  28. package/dist-lib/components/Plan/EditPlan/EditPlan.js +11 -10
  29. package/dist-lib/components/Plan/EditPlan/EditPlan.js.map +1 -1
  30. package/dist-lib/components/Plan/Mirrors/MirrorDetails.d.ts +12 -0
  31. package/dist-lib/components/Plan/Mirrors/MirrorDetails.d.ts.map +1 -0
  32. package/dist-lib/components/Plan/Mirrors/MirrorDetails.js +68 -0
  33. package/dist-lib/components/Plan/Mirrors/MirrorDetails.js.map +1 -0
  34. package/dist-lib/components/Plan/Mirrors/MirrorDetails.module.scss.js +26 -0
  35. package/dist-lib/components/Plan/Mirrors/MirrorDetails.module.scss.js.map +1 -0
  36. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.d.ts +11 -0
  37. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.d.ts.map +1 -0
  38. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.js +38 -0
  39. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.js.map +1 -0
  40. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.module.scss.js +16 -0
  41. package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.module.scss.js.map +1 -0
  42. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.d.ts +14 -0
  43. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.d.ts.map +1 -0
  44. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.js +54 -0
  45. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.js.map +1 -0
  46. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.module.scss.js +26 -0
  47. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.module.scss.js.map +1 -0
  48. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.d.ts +15 -0
  49. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.d.ts.map +1 -0
  50. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.js +34 -0
  51. package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.js.map +1 -0
  52. package/dist-lib/components/Plan/PlanBackups/PlanBackups.d.ts.map +1 -1
  53. package/dist-lib/components/Plan/PlanBackups/PlanBackups.js +20 -17
  54. package/dist-lib/components/Plan/PlanBackups/PlanBackups.js.map +1 -1
  55. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +2 -1
  56. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
  57. package/dist-lib/components/Plan/PlanForm/PlanForm.js +121 -94
  58. package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
  59. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +16 -0
  60. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -0
  61. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +115 -0
  62. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -0
  63. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +26 -0
  64. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -0
  65. package/dist-lib/components/Plan/PlanItems/PlanItem.d.ts.map +1 -1
  66. package/dist-lib/components/Plan/PlanItems/PlanItem.js +58 -59
  67. package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
  68. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts +2 -2
  69. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts.map +1 -1
  70. package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.js.map +1 -1
  71. package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js +8 -8
  72. package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js.map +1 -1
  73. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts +14 -0
  74. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts.map +1 -0
  75. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js +290 -0
  76. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js.map +1 -0
  77. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.module.scss.js +26 -0
  78. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.module.scss.js.map +1 -0
  79. package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
  80. package/dist-lib/components/Plan/PlanStats/PlanStats.js +41 -42
  81. package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
  82. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +5 -5
  83. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts +15 -0
  84. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts.map +1 -0
  85. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js +69 -0
  86. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js.map +1 -0
  87. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js +16 -0
  88. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js.map +1 -0
  89. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.d.ts.map +1 -1
  90. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js +36 -34
  91. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js.map +1 -1
  92. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.d.ts.map +1 -1
  93. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js +7 -5
  94. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js.map +1 -1
  95. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts +12 -4
  96. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts.map +1 -1
  97. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js +44 -32
  98. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js.map +1 -1
  99. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts +5 -1
  100. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts.map +1 -1
  101. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js +48 -44
  102. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js.map +1 -1
  103. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js +32 -32
  104. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts +1 -1
  105. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts.map +1 -1
  106. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +52 -24
  107. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
  108. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.d.ts.map +1 -1
  109. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js +28 -19
  110. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js.map +1 -1
  111. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.d.ts +7 -0
  112. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.d.ts.map +1 -0
  113. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.js +79 -0
  114. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.js.map +1 -0
  115. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss.js +24 -0
  116. package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss.js.map +1 -0
  117. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  118. package/dist-lib/components/common/Icon/Icon.js +11 -0
  119. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  120. package/dist-lib/components/common/PageHeader/PageHeader.module.scss.js +6 -6
  121. package/dist-lib/components/index.d.ts +6 -0
  122. package/dist-lib/components/index.d.ts.map +1 -1
  123. package/dist-lib/components.js +102 -90
  124. package/dist-lib/components.js.map +1 -1
  125. package/dist-lib/hooks/usePwaAutoUpdate.d.ts +11 -2
  126. package/dist-lib/hooks/usePwaAutoUpdate.d.ts.map +1 -1
  127. package/dist-lib/hooks/usePwaAutoUpdate.js +32 -10
  128. package/dist-lib/hooks/usePwaAutoUpdate.js.map +1 -1
  129. package/dist-lib/router.d.ts.map +1 -1
  130. package/dist-lib/router.js +46 -35
  131. package/dist-lib/router.js.map +1 -1
  132. package/dist-lib/routes/DeviceSingle/DeviceSingle.d.ts.map +1 -1
  133. package/dist-lib/routes/DeviceSingle/DeviceSingle.js +40 -40
  134. package/dist-lib/routes/DeviceSingle/DeviceSingle.js.map +1 -1
  135. package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
  136. package/dist-lib/routes/PlanSingle/PlanSingle.js +123 -98
  137. package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
  138. package/dist-lib/services/backups.d.ts +15 -2
  139. package/dist-lib/services/backups.d.ts.map +1 -1
  140. package/dist-lib/services/backups.js +119 -100
  141. package/dist-lib/services/backups.js.map +1 -1
  142. package/dist-lib/services/plans.d.ts +20 -0
  143. package/dist-lib/services/plans.d.ts.map +1 -1
  144. package/dist-lib/services/plans.js +227 -172
  145. package/dist-lib/services/plans.js.map +1 -1
  146. package/dist-lib/services/restores.d.ts +10 -2
  147. package/dist-lib/services/restores.d.ts.map +1 -1
  148. package/dist-lib/services/restores.js +61 -57
  149. package/dist-lib/services/restores.js.map +1 -1
  150. package/dist-lib/services/settings.d.ts +16 -0
  151. package/dist-lib/services/settings.d.ts.map +1 -1
  152. package/dist-lib/services/settings.js +147 -68
  153. package/dist-lib/services/settings.js.map +1 -1
  154. package/dist-lib/services/users.d.ts.map +1 -1
  155. package/dist-lib/services/users.js +32 -32
  156. package/dist-lib/services/users.js.map +1 -1
  157. package/dist-lib/services.js +113 -101
  158. package/dist-lib/styles/core-frontend.css +1 -1
  159. package/dist-lib/utils/progressHelpers.d.ts +12 -1
  160. package/dist-lib/utils/progressHelpers.d.ts.map +1 -1
  161. package/dist-lib/utils/progressHelpers.js +121 -63
  162. package/dist-lib/utils/progressHelpers.js.map +1 -1
  163. package/dist-lib/utils.js +29 -28
  164. package/package.json +1 -1
  165. package/src/@types/backups.ts +28 -0
  166. package/src/@types/devices.ts +8 -0
  167. package/src/@types/plans.ts +23 -1
  168. package/src/@types/restores.ts +2 -0
  169. package/src/components/Device/DeviceBackups/DeviceBackups.tsx +11 -36
  170. package/src/components/Plan/BackupEvents/BackupEvents.module.scss +65 -0
  171. package/src/components/Plan/BackupEvents/BackupEvents.tsx +65 -4
  172. package/src/components/Plan/BackupProgress/BackupProgress.module.scss +121 -3
  173. package/src/components/Plan/BackupProgress/BackupProgress.tsx +149 -71
  174. package/src/components/Plan/Backups/Backups.tsx +52 -4
  175. package/src/components/Plan/EditPlan/EditPlan.tsx +1 -0
  176. package/src/components/Plan/Mirrors/MirrorDetails.module.scss +76 -0
  177. package/src/components/Plan/Mirrors/MirrorDetails.tsx +100 -0
  178. package/src/components/Plan/Mirrors/MirrorStatusBadge.module.scss +25 -0
  179. package/src/components/Plan/Mirrors/MirrorStatusBadge.tsx +65 -0
  180. package/src/components/Plan/Mirrors/MirrorStorageSelector.module.scss +97 -0
  181. package/src/components/Plan/Mirrors/MirrorStorageSelector.tsx +70 -0
  182. package/src/components/Plan/Mirrors/MirrorStorageSelectorModal.tsx +40 -0
  183. package/src/components/Plan/PlanBackups/PlanBackups.tsx +4 -1
  184. package/src/components/Plan/PlanForm/PlanForm.tsx +44 -17
  185. package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +110 -0
  186. package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +187 -0
  187. package/src/components/Plan/PlanItems/PlanItem.tsx +3 -3
  188. package/src/components/Plan/PlanPendingBackup/PlanPendingBackup.tsx +2 -2
  189. package/src/components/Plan/PlanRemoveModal/PlanRemoveModal.tsx +1 -1
  190. package/src/components/Plan/PlanSettings/PlanReplicationSettings.module.scss +105 -0
  191. package/src/components/Plan/PlanSettings/PlanReplicationSettings.tsx +334 -0
  192. package/src/components/Plan/PlanStats/PlanStats.module.scss +1 -1
  193. package/src/components/Plan/PlanStats/PlanStats.tsx +8 -8
  194. package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss +43 -0
  195. package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.tsx +83 -0
  196. package/src/components/Restore/RestoreWizard/RestoreConfirmStep.tsx +2 -0
  197. package/src/components/Restore/RestoreWizard/RestorePreviewStep.tsx +2 -0
  198. package/src/components/Restore/RestoreWizard/RestoreSettingsStep.tsx +36 -13
  199. package/src/components/Restore/RestoreWizard/RestoreWizard.module.scss +4 -0
  200. package/src/components/Restore/RestoreWizard/RestoreWizard.tsx +9 -1
  201. package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +38 -2
  202. package/src/components/Settings/IntegrationSettings/IntegrationSettings.tsx +9 -2
  203. package/src/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss +62 -0
  204. package/src/components/Settings/TwoFactorSetup/TwoFactorSetup.tsx +102 -0
  205. package/src/components/common/Icon/Icon.tsx +10 -1
  206. package/src/components/common/PageHeader/PageHeader.module.scss +3 -0
  207. package/src/components/index.ts +8 -0
  208. package/src/hooks/usePwaAutoUpdate.ts +51 -11
  209. package/src/router.tsx +26 -17
  210. package/src/routes/DeviceSingle/DeviceSingle.tsx +3 -3
  211. package/src/routes/PlanSingle/PlanSingle.tsx +21 -0
  212. package/src/services/backups.ts +32 -9
  213. package/src/services/plans.ts +75 -0
  214. package/src/services/restores.ts +10 -2
  215. package/src/services/settings.ts +90 -0
  216. package/src/services/users.ts +14 -5
  217. package/src/utils/progressHelpers.ts +85 -1
@@ -12,6 +12,8 @@ import RestoreWizard from '../../Restore/RestoreWizard/RestoreWizard';
12
12
  import StatusLabel from '../../common/StatusLabel/StatusLabel';
13
13
  import BackupEvents from '../BackupEvents/BackupEvents';
14
14
  import Input from '../../common/form/Input/Input';
15
+ import MirrorStatusBadge from '../Mirrors/MirrorStatusBadge';
16
+ import MirrorStorageSelectorModal from '../Mirrors/MirrorStorageSelectorModal';
15
17
 
16
18
  const DownloadLabel = ({ download, downloadBackup }: { download: Backup['download']; downloadBackup: () => void }) => {
17
19
  if (download?.status === 'started') {
@@ -67,6 +69,9 @@ const Backups = ({
67
69
  backups = [],
68
70
  sourceId,
69
71
  sourceType,
72
+ replicationSettings,
73
+ storage,
74
+ deviceId,
70
75
  // snapLimit,
71
76
  }: {
72
77
  planId: string;
@@ -75,12 +80,20 @@ const Backups = ({
75
80
  sourceId: string;
76
81
  sourceType: string;
77
82
  snapLimit: number;
83
+ deviceId: string;
84
+ storage: {
85
+ id: string;
86
+ type: string;
87
+ name: string;
88
+ };
89
+ replicationSettings: Plan['settings']['replication'];
78
90
  }) => {
79
91
  const [showSnapOptions, setShowSnapOptions] = useState<false | string>(false);
80
92
  const [showDeleteModal, setShowDeleteModal] = useState<Backup | false>(false);
81
93
  const [showRestoreModal, setShowRestoreModal] = useState<Backup | false>(false);
82
94
  const [showBackupEvents, setShowBackupEvents] = useState<false | string>(false);
83
95
  const [showEditModal, setShowEditModal] = useState<Backup | false>(false);
96
+ const [showStorageSelector, setShowStorageSelector] = useState<Backup | false>(false);
84
97
  const queryClient = useQueryClient();
85
98
  const deleteBackupMutation = useDeleteBackup();
86
99
  const updateBackupMutation = useUpdateBackup();
@@ -129,8 +142,16 @@ const Backups = ({
129
142
  );
130
143
  };
131
144
 
132
- const downloadBackup = (backupId: string) => {
133
- toast.promise(downloadBackupMutation.mutateAsync({ backupId, planId }), {
145
+ const handleDownloadClick = (backup: Backup) => {
146
+ if (replicationSettings?.enabled && backup.mirrors && backup.mirrors.some((m) => m.status === 'completed')) {
147
+ setShowStorageSelector(backup);
148
+ } else {
149
+ downloadBackup(backup.id);
150
+ }
151
+ };
152
+
153
+ const downloadBackup = (backupId: string, replicationId?: string) => {
154
+ toast.promise(downloadBackupMutation.mutateAsync({ backupId, planId, replicationId }), {
134
155
  pending: 'Sending Download Request...',
135
156
  success: 'Generating Download. This might take a while..',
136
157
  error: {
@@ -199,6 +220,9 @@ const Backups = ({
199
220
  <Icon type="bolt" size={14} />
200
221
  </span>
201
222
  )}
223
+ {replicationSettings?.enabled && snapshot.mirrors && snapshot.mirrors.length > 0 && (
224
+ <MirrorStatusBadge mirrors={snapshot.mirrors} planId={planId} backupId={id} replicationSettings={replicationSettings} />
225
+ )}
202
226
  {download && <DownloadLabel download={download} downloadBackup={() => getDownloadMutation.mutate(id)} />}
203
227
  </div>
204
228
  <div
@@ -257,7 +281,7 @@ const Backups = ({
257
281
  if (isDownloading) {
258
282
  cancelDownload(id);
259
283
  } else {
260
- downloadBackup(id);
284
+ handleDownloadClick(snapshot);
261
285
  }
262
286
  setShowSnapOptions(false);
263
287
  }}
@@ -319,7 +343,20 @@ const Backups = ({
319
343
  }}
320
344
  />
321
345
  )}
322
- {showRestoreModal && <RestoreWizard close={() => setShowRestoreModal(false)} planId={planId} backupId={showRestoreModal.id} />}
346
+ {showRestoreModal && (
347
+ <RestoreWizard
348
+ close={() => setShowRestoreModal(false)}
349
+ planId={planId}
350
+ backupId={showRestoreModal.id}
351
+ deviceId={deviceId}
352
+ planStorage={storage}
353
+ mirrors={
354
+ replicationSettings?.enabled && showRestoreModal.mirrors && showRestoreModal.mirrors.some((m) => m.status === 'completed')
355
+ ? showRestoreModal.mirrors
356
+ : []
357
+ }
358
+ />
359
+ )}
323
360
  {showBackupEvents && (
324
361
  <BackupEvents
325
362
  id={showBackupEvents}
@@ -366,6 +403,17 @@ const Backups = ({
366
403
  }}
367
404
  />
368
405
  )}
406
+ {showStorageSelector && (
407
+ <MirrorStorageSelectorModal
408
+ mirrors={showStorageSelector.mirrors || []}
409
+ primaryStorage={storage}
410
+ onSelect={(replicationId) => {
411
+ downloadBackup(showStorageSelector.id, replicationId);
412
+ setShowStorageSelector(false);
413
+ }}
414
+ onClose={() => setShowStorageSelector(false)}
415
+ />
416
+ )}
369
417
  </div>
370
418
  );
371
419
  };
@@ -42,6 +42,7 @@ const EditPlan = ({ close, plan }: EditPlanProps) => {
42
42
  <PlanForm
43
43
  title="Edit Plan"
44
44
  type="edit"
45
+ planId={plan.id}
45
46
  planSettings={newPlan}
46
47
  isSubmitting={updatePlanMutation.isPending}
47
48
  storagePath={plan.storagePath}
@@ -0,0 +1,76 @@
1
+ .mirrorStats {
2
+ display: flex;
3
+ flex-direction: row;
4
+ justify-content: space-between;
5
+ padding: 12px 4px;
6
+ }
7
+
8
+ .mirrorList {
9
+ border: 1px solid var(--line-color);
10
+ border-radius: 6px;
11
+ .mirrorItem {
12
+ padding: 12px 20px;
13
+ border-bottom: 1px solid var(--line-color);
14
+
15
+ .mirrorItemContent {
16
+ display: flex;
17
+ flex-direction: row;
18
+ justify-content: space-between;
19
+ }
20
+ img {
21
+ max-width: 14px;
22
+ vertical-align: middle;
23
+ margin-right: 2px;
24
+ }
25
+ .mirrorItemLabel {
26
+ i {
27
+ color: var(--content-text-color-light);
28
+ font-size: 0.7rem;
29
+ font-style: normal;
30
+ margin-left: 5px;
31
+ }
32
+ }
33
+ .mirrorItemStatus {
34
+ .retryButton {
35
+ border-radius: 12px;
36
+ padding: 2px 12px;
37
+ margin-right: 5px;
38
+ border: 1px solid var(--line-active-color);
39
+ cursor: pointer;
40
+ :global(.icon) {
41
+ color: var(--icon-color) !important;
42
+ }
43
+ &:hover {
44
+ background-color: var(--primary-color);
45
+ color: white;
46
+ border-color: var(--primary-color);
47
+ :global(.icon) {
48
+ color: white !important;
49
+ }
50
+ }
51
+ }
52
+ span {
53
+ color: var(--icon-color);
54
+ }
55
+ &.mirrorItemStatus.completed {
56
+ span {
57
+ color: var(--success-text-color);
58
+ }
59
+ }
60
+ &.mirrorItemStatus.failed {
61
+ span {
62
+ color: var(--error-button-color);
63
+ }
64
+ }
65
+ }
66
+ .mirrorItemError {
67
+ width: 100%;
68
+ background-color: var(--error-bg-color);
69
+ color: var(--error-text-color);
70
+ border-radius: 6px;
71
+ padding: 12px;
72
+ box-sizing: border-box;
73
+ margin-top: 12px;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,100 @@
1
+ import { toast } from 'react-toastify';
2
+ import { Backup, PlanReplicationSettings } from '../../../@types';
3
+ import { useRetryFailedReplications } from '../../../services';
4
+ import { formatBytes, formatDuration } from '../../../utils';
5
+ import Icon from '../../common/Icon/Icon';
6
+ import Modal from '../../common/Modal/Modal';
7
+ import classes from './MirrorDetails.module.scss';
8
+
9
+ interface MirrorDetailsProps {
10
+ mirrors: Backup['mirrors'];
11
+ replicationSettings?: PlanReplicationSettings;
12
+ backupId: string;
13
+ backupTitle?: string;
14
+ planId: string;
15
+ close: () => void;
16
+ }
17
+
18
+ const MirrorDetails = ({ mirrors = [], backupId, backupTitle, planId, close }: MirrorDetailsProps) => {
19
+ const retryReplicationsMutation = useRetryFailedReplications();
20
+
21
+ const retryReplication = (backupId: string, replicationId: string) => {
22
+ toast.promise(retryReplicationsMutation.mutateAsync({ backupId, planId, replicationId }), {
23
+ pending: 'Retrying failed replication...',
24
+ success: 'Replication retry queued!',
25
+ error: {
26
+ render({ data }: any) {
27
+ return `Failed to retry replication. ${data?.message || 'Unknown Error.'}`;
28
+ },
29
+ },
30
+ });
31
+ };
32
+
33
+ return (
34
+ <Modal title={`Mirrors for ${backupTitle ? backupTitle : 'backup-' + backupId}`} closeModal={close} width="560px">
35
+ <div>
36
+ <div className={classes.mirrorList}>
37
+ {mirrors && mirrors.length > 0 ? (
38
+ mirrors.map((mirror, index) => {
39
+ const duration = mirror.ended && mirror.started ? formatDuration((mirror.ended - mirror.started) / 1000) : 'N/A';
40
+ const mirrorSize = mirror.size ? formatBytes(mirror.size) : '';
41
+ const label =
42
+ mirror.status === 'completed'
43
+ ? 'Completed'
44
+ : mirror.status === 'failed'
45
+ ? 'Failed'
46
+ : mirror.status === 'started'
47
+ ? 'In Progress'
48
+ : 'Pending';
49
+ const iconType =
50
+ mirror.status === 'completed'
51
+ ? 'check'
52
+ : mirror.status === 'failed'
53
+ ? 'error'
54
+ : mirror.status === 'started'
55
+ ? 'loading'
56
+ : 'clock';
57
+ return (
58
+ <div key={index} className={classes.mirrorItem}>
59
+ <div className={classes.mirrorItemContent}>
60
+ <div className={classes.mirrorItemLabel}>
61
+ <img src={`/providers/${mirror.storageType}.png`} /> {mirror.storageName}{' '}
62
+ {mirror.status === 'completed' && (
63
+ <>
64
+ {mirrorSize && (
65
+ <i>
66
+ <Icon type="disk" size={12} /> {mirrorSize}
67
+ </i>
68
+ )}{' '}
69
+ <i>
70
+ <Icon type="clock" size={12} /> {duration}
71
+ </i>
72
+ </>
73
+ )}
74
+ </div>
75
+ <div className={`${classes.mirrorItemStatus} ${classes[mirror.status]}`}>
76
+ {mirror.status === 'failed' && (
77
+ <button
78
+ className={classes.retryButton}
79
+ onClick={() => !retryReplicationsMutation.isPending && retryReplication(backupId, mirror.replicationId)}
80
+ >
81
+ <Icon type={'reload'} size={14} /> Retry
82
+ </button>
83
+ )}{' '}
84
+ <Icon type={iconType} size={12} /> {label}
85
+ </div>
86
+ </div>
87
+ {mirror.error && <div className={classes.mirrorItemError}>{mirror.error}</div>}
88
+ </div>
89
+ );
90
+ })
91
+ ) : (
92
+ <div>No mirrors available.</div>
93
+ )}
94
+ </div>
95
+ </div>
96
+ </Modal>
97
+ );
98
+ };
99
+
100
+ export default MirrorDetails;
@@ -0,0 +1,25 @@
1
+ .badge {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 3px;
5
+ padding: 1px 4px;
6
+ cursor: default;
7
+ white-space: nowrap;
8
+ cursor: pointer;
9
+ }
10
+
11
+ .badgeSuccess {
12
+ color: var(--success-text-color);
13
+ }
14
+
15
+ .badgePartial {
16
+ color: var(--warning-button-color);
17
+ }
18
+
19
+ .badgeFailed {
20
+ color: var(--error-text-color);
21
+ }
22
+
23
+ .badgeProgress {
24
+ color: #7e90c5;
25
+ }
@@ -0,0 +1,65 @@
1
+ import { useState } from 'react';
2
+ import { BackupMirror } from '../../../@types/backups';
3
+ import Icon from '../../common/Icon/Icon';
4
+ import classes from './MirrorStatusBadge.module.scss';
5
+ import MirrorDetails from './MirrorDetails';
6
+ import { PlanReplicationSettings } from '../../../@types';
7
+
8
+ interface MirrorStatusBadgeProps {
9
+ mirrors: BackupMirror[];
10
+ planId: string;
11
+ backupId: string;
12
+ replicationSettings?: PlanReplicationSettings;
13
+ }
14
+
15
+ const MirrorStatusBadge = ({ mirrors, planId, backupId, replicationSettings }: MirrorStatusBadgeProps) => {
16
+ const [showMirrorDetails, setShowMirrorDetails] = useState(false);
17
+ if (!mirrors || mirrors.length === 0) return null;
18
+
19
+ const completed = mirrors.filter((m) => m.status === 'completed').length;
20
+ const failed = mirrors.filter((m) => m.status === 'failed').length;
21
+ const inProgress = mirrors.filter((m) => m.status === 'started' || m.status === 'pending').length;
22
+ const total = mirrors.length;
23
+
24
+ let badgeClass = classes.badge;
25
+ let label: string;
26
+
27
+ if (inProgress > 0) {
28
+ badgeClass += ` ${classes.badgeProgress}`;
29
+ label = 'Replicating...';
30
+ } else if (failed === total) {
31
+ badgeClass += ` ${classes.badgeFailed}`;
32
+ label = 'Replication failed';
33
+ } else if (failed > 0) {
34
+ badgeClass += ` ${classes.badgePartial}`;
35
+ label = `${completed}/${total} mirrors`;
36
+ } else {
37
+ badgeClass += ` ${classes.badgeSuccess}`;
38
+ label = `${total} mirror${total > 1 ? 's' : ''}`;
39
+ }
40
+
41
+ return (
42
+ <>
43
+ <span
44
+ className={badgeClass}
45
+ data-tooltip-id="htmlToolTip"
46
+ data-tooltip-place="top"
47
+ data-tooltip-html={`<div>${label}</div>`}
48
+ onClick={() => setShowMirrorDetails(true)}
49
+ >
50
+ <Icon type={'mirrors'} size={13} />
51
+ </span>
52
+ {showMirrorDetails && (
53
+ <MirrorDetails
54
+ mirrors={mirrors}
55
+ planId={planId}
56
+ backupId={backupId}
57
+ replicationSettings={replicationSettings}
58
+ close={() => setShowMirrorDetails(false)}
59
+ />
60
+ )}
61
+ </>
62
+ );
63
+ };
64
+
65
+ export default MirrorStatusBadge;
@@ -0,0 +1,97 @@
1
+ .mirrorSelector {
2
+ .selectorContent {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 8px;
6
+ margin-top: 10px;
7
+ }
8
+
9
+ .storageOption {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 10px;
13
+ padding: 10px 12px;
14
+ border: 1px solid var(--line-color);
15
+ border-radius: 6px;
16
+ cursor: pointer;
17
+ transition: all 0.12s linear;
18
+ &:hover {
19
+ background-color: var(--primary-color-light);
20
+ }
21
+ }
22
+
23
+ .storageDisabled {
24
+ opacity: 0.5;
25
+ cursor: not-allowed;
26
+ &:hover {
27
+ border-color: var(--line-color);
28
+ background-color: transparent;
29
+ }
30
+ }
31
+
32
+ .radio {
33
+ color: var(--icon-color);
34
+ &.radioSelected {
35
+ color: var(--primary-color);
36
+ }
37
+ }
38
+
39
+ .storageName {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 6px;
43
+ img {
44
+ max-width: 20px;
45
+ vertical-align: middle;
46
+ }
47
+ .storageLabel {
48
+ font-size: 0.65rem;
49
+ border-radius: 12px;
50
+ padding: 1px 10px;
51
+ background-color: var(--background-color);
52
+ }
53
+ }
54
+
55
+ .statusLabel {
56
+ margin-left: auto;
57
+ font-size: 0.6875rem;
58
+ padding: 1px 8px;
59
+ border-radius: 12px;
60
+ text-transform: capitalize;
61
+ background-color: var(--line-color);
62
+ color: var(--content-text-color-light);
63
+ }
64
+
65
+ .noMirrors {
66
+ padding: 15px;
67
+ text-align: center;
68
+ color: var(--content-text-color-light);
69
+ font-size: 0.8125rem;
70
+ }
71
+
72
+ &.dropdown {
73
+ position: relative;
74
+ .dropdownLabel {
75
+ padding: 6px 10px;
76
+ border: 1px solid var(--line-color);
77
+ border-radius: 6px;
78
+ cursor: pointer;
79
+ min-width: 160px;
80
+ }
81
+ .selectorContent {
82
+ display: none;
83
+ position: absolute;
84
+ margin-top: 0px;
85
+ right: 0;
86
+ min-width: 260px;
87
+ z-index: 99;
88
+ padding: 10px;
89
+ background-color: var(--content-background-color);
90
+ border: 1px solid var(--line-color);
91
+ border-radius: 6px;
92
+ &.showOptions {
93
+ display: block;
94
+ }
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,70 @@
1
+ import { useState } from 'react';
2
+ import { BackupMirror } from '../../../@types/backups';
3
+ import Icon from '../../common/Icon/Icon';
4
+ import classes from './MirrorStorageSelector.module.scss';
5
+
6
+ interface MirrorStorageSelectorProps {
7
+ mirrors: BackupMirror[];
8
+ primaryStorage: { id: string; type: string; name: string };
9
+ onChange: (replicationId?: string) => void;
10
+ selectedReplicationId?: string;
11
+ }
12
+
13
+ const MirrorStorageSelector = ({ mirrors, primaryStorage, onChange, selectedReplicationId }: MirrorStorageSelectorProps) => {
14
+ const [selected, setSelected] = useState<string | undefined>(selectedReplicationId);
15
+ const completedMirrors = mirrors.filter((m) => m.status === 'completed');
16
+
17
+ return (
18
+ <div className={classes.mirrorSelector}>
19
+ <div className={classes.selectorContent}>
20
+ <div
21
+ className={classes.storageOption}
22
+ onClick={() => {
23
+ setSelected(undefined);
24
+ onChange(undefined);
25
+ }}
26
+ >
27
+ <span className={`${classes.radio} ${selected === undefined ? classes.radioSelected : ''}`}>
28
+ <Icon type={selected === undefined ? 'check-circle-filled' : 'check-circle'} size={16} />
29
+ </span>
30
+ <span className={classes.storageName}>
31
+ <img src={`/providers/${primaryStorage.type}.png`} />
32
+ {primaryStorage.name}
33
+ <span className={classes.storageLabel}>Primary</span>
34
+ </span>
35
+ </div>
36
+ {mirrors.map((mirror) => {
37
+ const isCompleted = mirror.status === 'completed';
38
+ const isSelected = selected === mirror.replicationId;
39
+ return (
40
+ <div
41
+ key={mirror.replicationId}
42
+ className={`${classes.storageOption} ${!isCompleted ? classes.storageDisabled : ''}`}
43
+ onClick={() => {
44
+ if (isCompleted) {
45
+ setSelected(mirror.replicationId);
46
+ onChange(mirror.replicationId);
47
+ }
48
+ }}
49
+ >
50
+ <span className={`${classes.radio} ${isSelected ? classes.radioSelected : ''}`}>
51
+ <Icon type={isSelected ? 'check-circle-filled' : 'check-circle'} size={16} />
52
+ </span>
53
+
54
+ <span className={classes.storageName}>
55
+ <img src={`/providers/${mirror.storageType}.png`} /> {mirror.storageName}
56
+ <span className={classes.storageLabel}>Mirror</span>
57
+ </span>
58
+ {!isCompleted && <span className={classes.statusLabel}>{mirror.status}</span>}
59
+ </div>
60
+ );
61
+ })}
62
+ {completedMirrors.length === 0 && (
63
+ <div className={classes.noMirrors}>No completed mirrors available. Only the primary storage can be used.</div>
64
+ )}
65
+ </div>
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default MirrorStorageSelector;
@@ -0,0 +1,40 @@
1
+ import { useState } from 'react';
2
+ import { BackupMirror } from '../../../@types/backups';
3
+ import ActionModal from '../../common/ActionModal/ActionModal';
4
+ import MirrorStorageSelector from './MirrorStorageSelector';
5
+
6
+ interface MirrorStorageSelectorProps {
7
+ mirrors: BackupMirror[];
8
+ primaryStorage: { id: string; type: string; name: string };
9
+ onSelect: (replicationId?: string) => void;
10
+ onClose: () => void;
11
+ actionLabel?: string;
12
+ }
13
+
14
+ const MirrorStorageSelectorModal = ({ mirrors, primaryStorage, onSelect, onClose, actionLabel = 'Download' }: MirrorStorageSelectorProps) => {
15
+ const [selected, setSelected] = useState<string | undefined>(undefined);
16
+
17
+ return (
18
+ <ActionModal
19
+ title={`Choose a Mirror to ${actionLabel} From`}
20
+ message={
21
+ <MirrorStorageSelector
22
+ mirrors={mirrors}
23
+ primaryStorage={primaryStorage}
24
+ onChange={(replicationId) => replicationId && setSelected(replicationId)}
25
+ />
26
+ }
27
+ closeModal={onClose}
28
+ width="450px"
29
+ primaryAction={{
30
+ title: actionLabel,
31
+ type: 'default',
32
+ isPending: false,
33
+ icon: 'download',
34
+ action: () => onSelect(selected),
35
+ }}
36
+ />
37
+ );
38
+ };
39
+
40
+ export default MirrorStorageSelectorModal;
@@ -14,7 +14,7 @@ const PlanBackups = ({ plan }: PlanBackupsProps) => {
14
14
  const AllBackups = useComponentOverride('Backups', Backups);
15
15
  const [historyTab, setHistoryTab] = useState<'backups' | 'restores'>('backups');
16
16
 
17
- const { backups = [], stats, restores = [], method, sourceId, sourceType, settings } = plan;
17
+ const { backups = [], stats, restores = [], method, sourceId, sourceType, settings, storage } = plan;
18
18
 
19
19
  const sortedHistory = [...(backups || [])].sort((a, b) => new Date(b.started).getTime() - new Date(a.started).getTime());
20
20
  const isSync = method === 'sync';
@@ -44,7 +44,10 @@ const PlanBackups = ({ plan }: PlanBackupsProps) => {
44
44
  method={method}
45
45
  sourceId={sourceId}
46
46
  sourceType={sourceType}
47
+ deviceId={plan.device.id}
48
+ storage={storage}
47
49
  snapLimit={settings.prune.snapCount}
50
+ replicationSettings={settings.replication}
48
51
  />
49
52
  )}
50
53
  {historyTab === 'restores' && <Restores restores={restores} planId={plan.id} method={method} sourceId={sourceId} sourceType={sourceType} />}