@plutonhq/core-frontend 0.1.13 → 0.1.14

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 (186) 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 +85 -58
  58. package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
  59. package/dist-lib/components/Plan/PlanItems/PlanItem.d.ts.map +1 -1
  60. package/dist-lib/components/Plan/PlanItems/PlanItem.js +58 -59
  61. package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
  62. package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js +8 -8
  63. package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js.map +1 -1
  64. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts +14 -0
  65. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts.map +1 -0
  66. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js +290 -0
  67. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js.map +1 -0
  68. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.module.scss.js +26 -0
  69. package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.module.scss.js.map +1 -0
  70. package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
  71. package/dist-lib/components/Plan/PlanStats/PlanStats.js +41 -42
  72. package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
  73. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +5 -5
  74. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts +15 -0
  75. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts.map +1 -0
  76. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js +69 -0
  77. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js.map +1 -0
  78. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js +16 -0
  79. package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js.map +1 -0
  80. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.d.ts.map +1 -1
  81. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js +36 -34
  82. package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js.map +1 -1
  83. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.d.ts.map +1 -1
  84. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js +7 -5
  85. package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js.map +1 -1
  86. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts +12 -4
  87. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts.map +1 -1
  88. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js +44 -32
  89. package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js.map +1 -1
  90. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts +5 -1
  91. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts.map +1 -1
  92. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js +48 -44
  93. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js.map +1 -1
  94. package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js +32 -32
  95. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +14 -14
  96. package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
  97. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.d.ts.map +1 -1
  98. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js +28 -19
  99. package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js.map +1 -1
  100. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  101. package/dist-lib/components/common/Icon/Icon.js +11 -0
  102. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  103. package/dist-lib/components/common/PageHeader/PageHeader.module.scss.js +6 -6
  104. package/dist-lib/components/index.d.ts +4 -0
  105. package/dist-lib/components/index.d.ts.map +1 -1
  106. package/dist-lib/components.js +86 -78
  107. package/dist-lib/components.js.map +1 -1
  108. package/dist-lib/hooks/usePwaAutoUpdate.d.ts +11 -2
  109. package/dist-lib/hooks/usePwaAutoUpdate.d.ts.map +1 -1
  110. package/dist-lib/hooks/usePwaAutoUpdate.js +32 -10
  111. package/dist-lib/hooks/usePwaAutoUpdate.js.map +1 -1
  112. package/dist-lib/router.d.ts.map +1 -1
  113. package/dist-lib/router.js +46 -35
  114. package/dist-lib/router.js.map +1 -1
  115. package/dist-lib/routes/DeviceSingle/DeviceSingle.d.ts.map +1 -1
  116. package/dist-lib/routes/DeviceSingle/DeviceSingle.js +40 -40
  117. package/dist-lib/routes/DeviceSingle/DeviceSingle.js.map +1 -1
  118. package/dist-lib/services/backups.d.ts +15 -2
  119. package/dist-lib/services/backups.d.ts.map +1 -1
  120. package/dist-lib/services/backups.js +119 -100
  121. package/dist-lib/services/backups.js.map +1 -1
  122. package/dist-lib/services/plans.d.ts +14 -0
  123. package/dist-lib/services/plans.d.ts.map +1 -1
  124. package/dist-lib/services/plans.js +160 -129
  125. package/dist-lib/services/plans.js.map +1 -1
  126. package/dist-lib/services/restores.d.ts +10 -2
  127. package/dist-lib/services/restores.d.ts.map +1 -1
  128. package/dist-lib/services/restores.js +61 -57
  129. package/dist-lib/services/restores.js.map +1 -1
  130. package/dist-lib/services/users.d.ts.map +1 -1
  131. package/dist-lib/services/users.js +32 -32
  132. package/dist-lib/services/users.js.map +1 -1
  133. package/dist-lib/services.js +107 -103
  134. package/dist-lib/styles/core-frontend.css +1 -1
  135. package/dist-lib/utils/progressHelpers.d.ts +12 -1
  136. package/dist-lib/utils/progressHelpers.d.ts.map +1 -1
  137. package/dist-lib/utils/progressHelpers.js +121 -63
  138. package/dist-lib/utils/progressHelpers.js.map +1 -1
  139. package/dist-lib/utils.js +29 -28
  140. package/package.json +1 -1
  141. package/src/@types/backups.ts +28 -0
  142. package/src/@types/devices.ts +8 -0
  143. package/src/@types/plans.ts +23 -1
  144. package/src/@types/restores.ts +2 -0
  145. package/src/components/Device/DeviceBackups/DeviceBackups.tsx +11 -36
  146. package/src/components/Plan/BackupEvents/BackupEvents.module.scss +65 -0
  147. package/src/components/Plan/BackupEvents/BackupEvents.tsx +65 -4
  148. package/src/components/Plan/BackupProgress/BackupProgress.module.scss +121 -3
  149. package/src/components/Plan/BackupProgress/BackupProgress.tsx +149 -71
  150. package/src/components/Plan/Backups/Backups.tsx +52 -4
  151. package/src/components/Plan/EditPlan/EditPlan.tsx +1 -0
  152. package/src/components/Plan/Mirrors/MirrorDetails.module.scss +76 -0
  153. package/src/components/Plan/Mirrors/MirrorDetails.tsx +100 -0
  154. package/src/components/Plan/Mirrors/MirrorStatusBadge.module.scss +25 -0
  155. package/src/components/Plan/Mirrors/MirrorStatusBadge.tsx +65 -0
  156. package/src/components/Plan/Mirrors/MirrorStorageSelector.module.scss +97 -0
  157. package/src/components/Plan/Mirrors/MirrorStorageSelector.tsx +70 -0
  158. package/src/components/Plan/Mirrors/MirrorStorageSelectorModal.tsx +40 -0
  159. package/src/components/Plan/PlanBackups/PlanBackups.tsx +4 -1
  160. package/src/components/Plan/PlanForm/PlanForm.tsx +30 -3
  161. package/src/components/Plan/PlanItems/PlanItem.tsx +3 -3
  162. package/src/components/Plan/PlanRemoveModal/PlanRemoveModal.tsx +1 -1
  163. package/src/components/Plan/PlanSettings/PlanReplicationSettings.module.scss +105 -0
  164. package/src/components/Plan/PlanSettings/PlanReplicationSettings.tsx +334 -0
  165. package/src/components/Plan/PlanStats/PlanStats.module.scss +1 -1
  166. package/src/components/Plan/PlanStats/PlanStats.tsx +8 -8
  167. package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss +43 -0
  168. package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.tsx +83 -0
  169. package/src/components/Restore/RestoreWizard/RestoreConfirmStep.tsx +2 -0
  170. package/src/components/Restore/RestoreWizard/RestorePreviewStep.tsx +2 -0
  171. package/src/components/Restore/RestoreWizard/RestoreSettingsStep.tsx +36 -13
  172. package/src/components/Restore/RestoreWizard/RestoreWizard.module.scss +4 -0
  173. package/src/components/Restore/RestoreWizard/RestoreWizard.tsx +9 -1
  174. package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +1 -1
  175. package/src/components/Settings/IntegrationSettings/IntegrationSettings.tsx +9 -2
  176. package/src/components/common/Icon/Icon.tsx +10 -1
  177. package/src/components/common/PageHeader/PageHeader.module.scss +3 -0
  178. package/src/components/index.ts +6 -0
  179. package/src/hooks/usePwaAutoUpdate.ts +51 -11
  180. package/src/router.tsx +26 -17
  181. package/src/routes/DeviceSingle/DeviceSingle.tsx +3 -3
  182. package/src/services/backups.ts +32 -9
  183. package/src/services/plans.ts +45 -0
  184. package/src/services/restores.ts +10 -2
  185. package/src/services/users.ts +14 -5
  186. package/src/utils/progressHelpers.ts +85 -1
@@ -8,6 +8,7 @@ import classes from './PlanItem.module.scss';
8
8
  import { useDeletePlan, usePausePlan, usePerformBackup, useResumePlan } from '../../../services/plans';
9
9
  import { planIntervalName } from '../../../utils/plans';
10
10
  import PlanHistory from '../PlanHistory/PlanHistory';
11
+ import PlanStorageInfo from '../PlanStorageInfo/PlanStorageInfo';
11
12
 
12
13
  interface PlanItemProps {
13
14
  plan: Plan;
@@ -156,9 +157,8 @@ const PlanItem = ({ plan, layout = 'list' }: PlanItemProps) => {
156
157
  )}
157
158
  <span className={classes.sourceCount}>{sourceConfig.includes.length}</span>
158
159
  </span>{' '}
159
- {'-->'} {storage.type && <img src={`/providers/${storage.type}.png`} />}
160
- {storage.name}
161
- {/* {storagePath && storagePath !== '/' && '/' + storagePath} */}
160
+ {'-->'}
161
+ <PlanStorageInfo replicationSettings={plan.settings.replication} storage={storage} storagePath={plan.storagePath} />
162
162
  </div>
163
163
  </NavLink>
164
164
  </div>
@@ -51,7 +51,7 @@ const PlanRemoveModal = ({ planId, taskPending, actionInProgress, close }: PlanR
51
51
  <Toggle
52
52
  fieldValue={removeRemoteData}
53
53
  onUpdate={setRemoveRemoteData}
54
- description={`Remove remote backup data from the Remote Storage`}
54
+ description={`Remove backup data from the Storage`}
55
55
  customClasses={classes.removeRemoteToggle}
56
56
  />
57
57
  </div>
@@ -0,0 +1,105 @@
1
+ .mirrorSection {
2
+ margin-top: 10px;
3
+ padding: 15px 0;
4
+ border-top: 1px solid var(--line-color);
5
+ }
6
+
7
+ .mirrorSettings {
8
+ margin-top: 15px;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 15px;
12
+ }
13
+
14
+ .mirrorDestination {
15
+ padding: 15px;
16
+ border: 1px solid var(--field-line-color);
17
+ border-radius: 6px;
18
+ }
19
+
20
+ .mirrorHeader {
21
+ display: flex;
22
+ justify-content: space-between;
23
+ align-items: center;
24
+ margin-bottom: 10px;
25
+
26
+ label {
27
+ margin-bottom: 0 !important;
28
+ }
29
+ }
30
+
31
+ .mirrorHeaderRight {
32
+ display: flex;
33
+ gap: 6px;
34
+ .confirmBtn {
35
+ color: var(--primary-color);
36
+ background-color: var(--primary-color-light);
37
+ font-size: 0.7rem;
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 4px;
41
+ padding: 4px 8px;
42
+ border-radius: 4px;
43
+ transition: all 0.12s linear;
44
+ &:hover:not(:disabled) {
45
+ background-color: var(--primary-color);
46
+ color: var(--primary-color-light);
47
+ }
48
+ &:disabled {
49
+ opacity: 0.6;
50
+ cursor: not-allowed;
51
+ }
52
+ }
53
+ .removeBtn {
54
+ color: var(--error-button-color);
55
+ font-size: 0.75rem;
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 4px;
59
+ padding: 4px 8px;
60
+ border-radius: 4px;
61
+ transition: all 0.12s linear;
62
+ &:hover {
63
+ background-color: var(--error-bg-color);
64
+ }
65
+ }
66
+
67
+ .storagePositionControls {
68
+ button {
69
+ color: var(--icon-color);
70
+ &:hover {
71
+ color: var(--primary-color);
72
+ }
73
+ &:disabled {
74
+ opacity: 0.4;
75
+ color: var(--icon-color);
76
+ cursor: not-allowed;
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ .addMirrorBtn {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 6px;
86
+ padding: 10px 16px;
87
+ border: 1px dashed var(--field-line-color);
88
+ border-radius: 6px;
89
+ color: var(--content-text-color-light);
90
+ font-size: 0.75rem;
91
+ width: 100%;
92
+ justify-content: center;
93
+ transition: all 0.12s linear;
94
+ &:hover {
95
+ border-color: var(--primary-color);
96
+ color: var(--primary-color);
97
+ background-color: var(--primary-color-light);
98
+ }
99
+ }
100
+
101
+ .concurrentOption {
102
+ margin-top: 5px;
103
+ padding-top: 10px;
104
+ border-top: 1px solid var(--line-color);
105
+ }
@@ -0,0 +1,334 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { PlanReplicationSettings as ReplicationSettings, PlanReplicationStorage } from '../../../@types/plans';
3
+ import Icon from '../../common/Icon/Icon';
4
+ import StoragePicker from '../../common/form/StoragePicker/StoragePicker';
5
+ import Toggle from '../../common/form/Toggle/Toggle';
6
+ import classes from '../../Plan/AddPlan/AddPlan.module.scss';
7
+ import mirrorClasses from './PlanReplicationSettings.module.scss';
8
+ import ActionModal from '../../common/ActionModal/ActionModal';
9
+ import { useDeleteReplicationStorage } from '../../../services';
10
+ import { toast } from 'react-toastify';
11
+ import { nanoid } from 'nanoid';
12
+
13
+ interface PlanReplicationSettingsProps {
14
+ replication?: ReplicationSettings;
15
+ primaryStorageId: string;
16
+ primaryStoragePath?: string;
17
+ deviceId: string;
18
+ isEditing?: boolean;
19
+ planID?: string;
20
+ maxReplications?: number;
21
+ onUpdate: (replication: ReplicationSettings) => void;
22
+ }
23
+
24
+ const defaultReplication: ReplicationSettings = {
25
+ enabled: false,
26
+ concurrent: false,
27
+ storages: [],
28
+ };
29
+
30
+ const MAX_REPLICATIONS = 2;
31
+
32
+ const PlanReplicationSettings = ({
33
+ replication,
34
+ primaryStorageId,
35
+ primaryStoragePath,
36
+ deviceId,
37
+ isEditing = false,
38
+ planID,
39
+ maxReplications: maxReplicationsProp,
40
+ onUpdate,
41
+ }: PlanReplicationSettingsProps) => {
42
+ const [tempStorages, setTempStorages] = useState<PlanReplicationStorage[]>([]);
43
+ const [removeStorageData, setRemoveStorageData] = useState<boolean>(false);
44
+ const [showRemoveModal, setShowRemoveModal] = useState<boolean | PlanReplicationStorage>(false);
45
+
46
+ const deleteStorageMutation = useDeleteReplicationStorage();
47
+ const settings = replication || defaultReplication;
48
+
49
+ /** Check if a storage+path combination already exists in committed or temp storages */
50
+ const isDuplicateStorage = useCallback(
51
+ (storageId: string, storagePath: string, excludeCommittedIndex?: number, excludeTempIndex?: number) => {
52
+ // Skip duplicate check if storageId or storagePath is empty (incomplete selection)
53
+ if (!storageId || !storagePath) return false;
54
+ const committedDuplicate = settings.storages.some(
55
+ (s, i) =>
56
+ (excludeCommittedIndex === undefined || i !== excludeCommittedIndex) && s.storageId === storageId && s.storagePath === storagePath,
57
+ );
58
+ const tempDuplicate = tempStorages.some(
59
+ (s, i) => (excludeTempIndex === undefined || i !== excludeTempIndex) && s.storageId === storageId && s.storagePath === storagePath,
60
+ );
61
+ return committedDuplicate || tempDuplicate;
62
+ },
63
+ [settings.storages, tempStorages],
64
+ );
65
+
66
+ const updateEnabled = (enabled: boolean) => {
67
+ onUpdate({ ...settings, enabled });
68
+ };
69
+
70
+ const updateConcurrent = (concurrent: boolean) => {
71
+ onUpdate({ ...settings, concurrent });
72
+ };
73
+
74
+ const addReplicationStorage = () => {
75
+ if (isEditing) {
76
+ const maxReplications = maxReplicationsProp ?? MAX_REPLICATIONS;
77
+ if (settings.storages.length + tempStorages.length >= maxReplications) return;
78
+ setTempStorages([...tempStorages, { replicationId: nanoid(), storageId: '', storagePath: '', storageType: '', addedAt: Date.now() }]);
79
+ } else {
80
+ const maxReplications = maxReplicationsProp ?? MAX_REPLICATIONS;
81
+ if (settings.storages.length >= maxReplications) return;
82
+ onUpdate({
83
+ ...settings,
84
+ storages: [...settings.storages, { replicationId: nanoid(), storageId: '', storagePath: '', storageType: '', addedAt: Date.now() }],
85
+ });
86
+ }
87
+ };
88
+
89
+ const updateTempStorage = (index: number, storage: PlanReplicationStorage) => {
90
+ setTempStorages((prev) => {
91
+ const updated = [...prev];
92
+ updated[index] = storage;
93
+ return updated;
94
+ });
95
+ };
96
+
97
+ const confirmAddStorage = (index: number) => {
98
+ const storage = tempStorages[index];
99
+ if (!storage.storageId) return;
100
+ if (storage.storageId === primaryStorageId && storage.storagePath === primaryStoragePath) {
101
+ toast.error('Cannot replicate to the same storage as the primary backup.');
102
+ return;
103
+ }
104
+ if (isDuplicateStorage(storage.storageId, storage.storagePath, undefined, index)) {
105
+ toast.error('This storage destination is already added.');
106
+ return;
107
+ }
108
+ setTempStorages((prev) => prev.filter((_, i) => i !== index));
109
+ onUpdate({ ...settings, storages: [...settings.storages, storage] });
110
+ };
111
+
112
+ const removeTempStorage = (index: number) => {
113
+ setTempStorages((prev) => prev.filter((_, i) => i !== index));
114
+ };
115
+
116
+ const updateReplicationStorage = (index: number, storage: PlanReplicationStorage) => {
117
+ const updated = [...settings.storages];
118
+ updated[index] = storage;
119
+ onUpdate({ ...settings, storages: updated });
120
+ };
121
+
122
+ const removeAddedStorage = (index: number) => {
123
+ const updated = settings.storages.filter((_, i) => i !== index);
124
+ onUpdate({ ...settings, storages: updated });
125
+ };
126
+
127
+ const removeReplicationStorage = () => {
128
+ if (typeof showRemoveModal === 'object' && planID) {
129
+ const storageToRemove = showRemoveModal;
130
+ deleteStorageMutation.mutate(
131
+ {
132
+ planID: planID,
133
+ storageID: storageToRemove.storageId,
134
+ removeData: removeStorageData,
135
+ storagePath: storageToRemove.storagePath,
136
+ replicationId: storageToRemove.replicationId,
137
+ },
138
+ {
139
+ onSuccess: () => {
140
+ // Remove the storage from local state and close modal
141
+ const updated = settings.storages.filter((s) => s.replicationId !== storageToRemove.replicationId);
142
+ onUpdate({ ...settings, storages: updated, ...(updated.length === 0 && { enabled: false }) });
143
+ setShowRemoveModal(false);
144
+ setRemoveStorageData(false);
145
+ deleteStorageMutation.reset();
146
+ },
147
+ },
148
+ );
149
+ }
150
+ };
151
+
152
+ const openRemoveModal = (mirror: PlanReplicationStorage) => {
153
+ setRemoveStorageData(false);
154
+ deleteStorageMutation.reset();
155
+ setShowRemoveModal(mirror);
156
+ };
157
+
158
+ return (
159
+ <div className={classes.field}>
160
+ <div className={mirrorClasses.mirrorSection}>
161
+ <Toggle
162
+ label="Enable Replication"
163
+ description="Replicate backups to additional storage destinations for redundancy (3-2-1 backup strategy)"
164
+ fieldValue={settings.enabled}
165
+ onUpdate={updateEnabled}
166
+ />
167
+
168
+ {settings.enabled && (
169
+ <div className={mirrorClasses.mirrorSettings}>
170
+ {settings.storages.map((mirror, index) => (
171
+ <div key={mirror.addedAt || index} className={mirrorClasses.mirrorDestination}>
172
+ <div className={mirrorClasses.mirrorHeader}>
173
+ <label className={classes.label}>Replication Destination {index + 1}</label>
174
+ <div className={mirrorClasses.mirrorHeaderRight}>
175
+ {settings.storages.length > 1 && (
176
+ <div className={mirrorClasses.storagePositionControls}>
177
+ <button
178
+ title="Move Up"
179
+ disabled={index === 0}
180
+ onClick={() => {
181
+ const updatedStorages = [...(settings.storages || [])];
182
+ const [movedStorage] = updatedStorages.splice(index, 1);
183
+ updatedStorages.splice(index - 1, 0, movedStorage);
184
+ onUpdate({ ...settings, storages: updatedStorages });
185
+ }}
186
+ >
187
+ <Icon type={'caret-up'} size={14} />
188
+ </button>
189
+ <button
190
+ title="Move Down"
191
+ disabled={index === settings.storages.length - 1}
192
+ onClick={() => {
193
+ const updatedStorages = [...(settings.storages || [])];
194
+ const [movedStorage] = updatedStorages.splice(index, 1);
195
+ updatedStorages.splice(index + 1, 0, movedStorage);
196
+ onUpdate({ ...settings, storages: updatedStorages });
197
+ }}
198
+ >
199
+ <Icon type={'caret-down'} size={14} />
200
+ </button>
201
+ </div>
202
+ )}
203
+ <button
204
+ className={mirrorClasses.removeBtn}
205
+ onClick={() => (isEditing ? openRemoveModal(mirror) : removeAddedStorage(index))}
206
+ title="Remove replication destination"
207
+ >
208
+ <Icon type="trash" size={14} /> Remove
209
+ </button>
210
+ </div>
211
+ </div>
212
+ <StoragePicker
213
+ storagePath={mirror.storagePath}
214
+ storageId={mirror.storageId}
215
+ deviceId={deviceId}
216
+ disabled={isEditing}
217
+ onUpdate={(s) => {
218
+ if (s.storage.id === primaryStorageId && s.path === primaryStoragePath) {
219
+ toast.error('Cannot replicate to the same storage as the primary backup.');
220
+ return;
221
+ }
222
+ updateReplicationStorage(index, {
223
+ replicationId: mirror.replicationId,
224
+ storageId: s.storage.id,
225
+ storagePath: s.path,
226
+ storageType: s.storage.type,
227
+ storageName: s.storage.name,
228
+ addedAt: mirror.addedAt,
229
+ });
230
+ }}
231
+ />
232
+ </div>
233
+ ))}
234
+
235
+ {isEditing &&
236
+ tempStorages.map((tempStorage, index) => (
237
+ <div key={tempStorage.addedAt} className={mirrorClasses.mirrorDestination}>
238
+ <div className={mirrorClasses.mirrorHeader}>
239
+ <label className={classes.label}>Replication Destination {settings.storages.length + index + 1}</label>
240
+ <div className={mirrorClasses.mirrorHeaderRight}>
241
+ <button
242
+ className={mirrorClasses.confirmBtn}
243
+ onClick={() => confirmAddStorage(index)}
244
+ disabled={!tempStorage.storageId}
245
+ title={!tempStorage.storageId ? 'Select Storage Type to add the storage' : 'Confirm replication destination'}
246
+ >
247
+ <Icon type="check" size={11} /> Confirm
248
+ </button>
249
+ <button
250
+ className={mirrorClasses.removeBtn}
251
+ onClick={() => removeTempStorage(index)}
252
+ title="Remove replication destination"
253
+ >
254
+ <Icon type="trash" size={14} /> Remove
255
+ </button>
256
+ </div>
257
+ </div>
258
+ <StoragePicker
259
+ storagePath={tempStorage.storagePath}
260
+ storageId={tempStorage.storageId}
261
+ deviceId={deviceId}
262
+ disabled={false}
263
+ onUpdate={(s) => {
264
+ updateTempStorage(index, {
265
+ replicationId: tempStorage.replicationId,
266
+ storageId: s.storage.id,
267
+ storagePath: s.path,
268
+ storageType: s.storage.type,
269
+ storageName: s.storage.name,
270
+ addedAt: tempStorage.addedAt,
271
+ });
272
+ }}
273
+ />
274
+ </div>
275
+ ))}
276
+
277
+ {settings.storages.length + (isEditing ? tempStorages.length : 0) < (maxReplicationsProp ?? MAX_REPLICATIONS) && (
278
+ <button className={mirrorClasses.addMirrorBtn} onClick={addReplicationStorage}>
279
+ <Icon type="plus" size={12} /> + Add Replication Destination
280
+ </button>
281
+ )}
282
+
283
+ {settings.storages.length > 1 && (
284
+ <div className={mirrorClasses.concurrentOption}>
285
+ <Toggle
286
+ label="Run replications concurrently"
287
+ description="Run replications in parallel to speed up the backup process"
288
+ fieldValue={settings.concurrent}
289
+ onUpdate={updateConcurrent}
290
+ />
291
+ </div>
292
+ )}
293
+ </div>
294
+ )}
295
+ </div>
296
+
297
+ {showRemoveModal && (
298
+ <ActionModal
299
+ title="Remove Replication Storage"
300
+ message={
301
+ <div>
302
+ <p>Are you sure you want to remove this Replication storage from this plan?</p>
303
+ <Toggle
304
+ fieldValue={removeStorageData}
305
+ onUpdate={() => setRemoveStorageData((prev) => !prev)}
306
+ description={`Remove replicated data from the Storage`}
307
+ customClasses={classes.removeRemoteToggle}
308
+ />
309
+ </div>
310
+ }
311
+ closeModal={() => {
312
+ if (!deleteStorageMutation.isPending) {
313
+ setShowRemoveModal(false);
314
+ setRemoveStorageData(false);
315
+ deleteStorageMutation.reset();
316
+ }
317
+ }}
318
+ width="400px"
319
+ errorMessage={deleteStorageMutation.isError && 'Error Removing Replication Storage!'}
320
+ successMessage={deleteStorageMutation.isSuccess && 'Removed Replication Storage Successfully!'}
321
+ primaryAction={{
322
+ title: 'Yes, Remove Storage',
323
+ type: 'danger',
324
+ icon: 'trash',
325
+ isPending: deleteStorageMutation.isPending,
326
+ action: () => removeReplicationStorage(),
327
+ }}
328
+ />
329
+ )}
330
+ </div>
331
+ );
332
+ };
333
+
334
+ export default PlanReplicationSettings;
@@ -109,7 +109,7 @@
109
109
  }
110
110
  }
111
111
  .health {
112
- width: 38%;
112
+ width: 33%;
113
113
  }
114
114
  }
115
115
 
@@ -3,6 +3,7 @@ import { Plan } from '../../../@types/plans';
3
3
  import { formatBytes, formatNumberToK } from '../../../utils/helpers';
4
4
  import PlanHistory from '../PlanHistory/PlanHistory';
5
5
  import classes from './PlanStats.module.scss';
6
+ import PlanStorageInfo from '../PlanStorageInfo/PlanStorageInfo';
6
7
 
7
8
  interface PlanStatsProps {
8
9
  plan: Plan;
@@ -55,14 +56,13 @@ const PlanStats = ({ plan, isSync, lastBackupItem }: PlanStatsProps) => {
55
56
  <Icon type={isActive ? (isSync ? 'reload' : 'copy') : 'pause'} size={16} />
56
57
  <div className={classes.sourceArrow}>→</div>
57
58
  </div>
58
- <div
59
- data-tooltip-id="htmlToolTip"
60
- data-tooltip-place="top"
61
- data-tooltip-html={`<div><strong>Storage:</strong> ${storage?.name}</div><div><strong>Path:</strong> ${storagePath || '/'}</div>`}
62
- >
63
- {storage?.type && <img src={`/providers/${storage?.type}.png`} />}
64
- {storage?.name}
65
- </div>
59
+ <PlanStorageInfo
60
+ disableTooltip={false}
61
+ inline={false}
62
+ replicationSettings={plan.settings.replication}
63
+ storage={storage}
64
+ storagePath={storagePath}
65
+ />
66
66
  </div>
67
67
  </div>
68
68
  <div className={classes.snapshots}>
@@ -0,0 +1,43 @@
1
+ .planStorages {
2
+ .storageWithReplications {
3
+ display: flex;
4
+ flex-direction: column;
5
+ .storageIcons {
6
+ display: inline-block;
7
+ img {
8
+ width: auto;
9
+ max-height: 14px;
10
+ display: inline-block !important;
11
+ position: relative;
12
+ margin-left: -10px;
13
+ padding: 3px;
14
+ border-radius: 50%;
15
+ background: var(--content-background-color);
16
+ border: 1px solid var(--line-color);
17
+ }
18
+ img:nth-child(1) {
19
+ z-index: 99;
20
+ margin-left: 0;
21
+ }
22
+ }
23
+ }
24
+ &.inline {
25
+ display: inline-block;
26
+ margin-left: 5px;
27
+ .storageWithReplications {
28
+ flex-direction: row;
29
+ align-items: center;
30
+ .storageIcons {
31
+ img {
32
+ margin-left: 0px;
33
+ padding: 0;
34
+ background: transparent;
35
+ border: none;
36
+ }
37
+ }
38
+ }
39
+ .storageName {
40
+ display: inline-block;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,83 @@
1
+ import { PlanReplicationSettings } from '../../../@types';
2
+ import classes from './PlanStorageInfo.module.scss';
3
+
4
+ interface PlanStorageInfoProps {
5
+ storage: { name: string; type: string; id: string };
6
+ storagePath: string;
7
+ replicationSettings?: PlanReplicationSettings;
8
+ disableTooltip?: boolean;
9
+ inline?: boolean;
10
+ }
11
+
12
+ const PlanStorageInfo = ({ replicationSettings, storage, storagePath, disableTooltip = true, inline = true }: PlanStorageInfoProps) => {
13
+ console.log('replicationSettings :', replicationSettings);
14
+ return (
15
+ <>
16
+ {replicationSettings && replicationSettings.enabled && replicationSettings.storages.length > 0 ? (
17
+ <div
18
+ className={`${classes.planStorages} ${inline ? classes.inline : ''}`}
19
+ data-tooltip-hidden={disableTooltip}
20
+ data-tooltip-id="htmlToolTip"
21
+ data-tooltip-place="top"
22
+ data-tooltip-html={`
23
+ <div style="display: flex; flex-direction: column; gap: 8px;">
24
+ <div>
25
+ <div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
26
+ <img style="width: 24px; height: 24px;" src="/providers/${storage?.type}.png" />
27
+ <div>
28
+ <strong style="display: block;">${storage?.name}</strong>
29
+ ${storagePath || '/'}
30
+ </div>
31
+ </div>
32
+ </div>
33
+ ${replicationSettings.storages
34
+ .slice(0, 3)
35
+ .map(
36
+ (s) => `
37
+ <div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
38
+ <img style="width: 24px; height: 24px;" src="/providers/${s?.storageType}.png" />
39
+ <div>
40
+ <strong style="display: block;">${s?.storageName} (Mirror)</strong>
41
+ ${s?.storagePath || '/'}
42
+ </div>
43
+ </div>
44
+ `,
45
+ )
46
+ .join('')}
47
+ </div>`}
48
+ >
49
+ <div className={classes.storageWithReplications}>
50
+ <div className={classes.storageIcons}>
51
+ <img src={`/providers/${storage?.type}.png`} />
52
+ {replicationSettings.storages.map((s, index) => (
53
+ <img key={s.replicationId} src={`/providers/${s.storageType}.png`} style={{ zIndex: 98 - index }} />
54
+ ))}
55
+ </div>
56
+ <div className={classes.storageName}>{replicationSettings.storages.length + 1} Storages</div>
57
+ </div>
58
+ </div>
59
+ ) : (
60
+ <div
61
+ className={`${classes.planStorages} ${inline ? classes.inline : ''}`}
62
+ data-tooltip-hidden={disableTooltip}
63
+ data-tooltip-id="htmlToolTip"
64
+ data-tooltip-place="top"
65
+ data-tooltip-html={`
66
+ <div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
67
+ <img style="width: 24px; height: 24px;" src="/providers/${storage?.type}.png" />
68
+ <div>
69
+ <strong style="display: block;">${storage?.name}</strong>
70
+ ${storagePath || '/'}
71
+ </div>
72
+ </div>
73
+ `}
74
+ >
75
+ <img src={`/providers/${storage?.type}.png`} />
76
+ <div className={classes.storageName}>{storage?.name}</div>
77
+ </div>
78
+ )}
79
+ </>
80
+ );
81
+ };
82
+
83
+ export default PlanStorageInfo;
@@ -33,6 +33,8 @@ const RestoreConfirmStep = ({ backupId, planId, settings, stats, snapshotsStats,
33
33
  includes: settings.includes,
34
34
  excludes: settings.excludes,
35
35
  deleteOption: settings.delete,
36
+ storageId: settings.storageId,
37
+ replicationId: settings.replicationId,
36
38
  },
37
39
  {
38
40
  onSuccess: (data: any, variables) => {
@@ -57,6 +57,8 @@ const RestorePreviewStep = ({ backupId, planId, settings, preview, nextLabel, go
57
57
  includes: settings.includes,
58
58
  excludes: settings.excludes,
59
59
  deleteOption: settings.delete,
60
+ storageId: settings.storageId,
61
+ replicationId: settings.replicationId,
60
62
  },
61
63
  {
62
64
  onSuccess: (data) => {