@plutonhq/core-frontend 0.1.32 → 0.1.33

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 (126) hide show
  1. package/dist-lib/@types/plans.d.ts +4 -0
  2. package/dist-lib/@types/plans.d.ts.map +1 -1
  3. package/dist-lib/components/Plan/AddPlan/AddPlan.d.ts.map +1 -1
  4. package/dist-lib/components/Plan/AddPlan/AddPlan.js +29 -24
  5. package/dist-lib/components/Plan/AddPlan/AddPlan.js.map +1 -1
  6. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +34 -33
  7. package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
  8. package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts +9 -0
  9. package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts.map +1 -0
  10. package/dist-lib/components/Plan/FilterPlans/FilterPlans.js +117 -0
  11. package/dist-lib/components/Plan/FilterPlans/FilterPlans.js.map +1 -0
  12. package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js +20 -0
  13. package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js.map +1 -0
  14. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +4 -2
  15. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
  16. package/dist-lib/components/Plan/PlanForm/PlanForm.js +33 -29
  17. package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
  18. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +2 -1
  19. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -1
  20. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +85 -57
  21. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -1
  22. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +11 -9
  23. package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -1
  24. package/dist-lib/components/Plan/PlanItems/PlanItem.js +1 -1
  25. package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
  26. package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts +9 -0
  27. package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts.map +1 -0
  28. package/dist-lib/components/Plan/PlanRepair/PlanRepair.js +262 -0
  29. package/dist-lib/components/Plan/PlanRepair/PlanRepair.js.map +1 -0
  30. package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js +14 -0
  31. package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js.map +1 -0
  32. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts +4 -2
  33. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts.map +1 -1
  34. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +24 -22
  35. package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
  36. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts +4 -2
  37. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts.map +1 -1
  38. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js +39 -28
  39. package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js.map +1 -1
  40. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +66 -64
  41. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
  42. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts +7 -0
  43. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts.map +1 -0
  44. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js +116 -0
  45. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js.map +1 -0
  46. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js +20 -0
  47. package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js.map +1 -0
  48. package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
  49. package/dist-lib/components/Plan/PlanStats/PlanStats.js +29 -30
  50. package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
  51. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +16 -14
  52. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js.map +1 -1
  53. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  54. package/dist-lib/components/common/Icon/Icon.js +395 -378
  55. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  56. package/dist-lib/components/common/SortItems/SortItems.d.ts +2 -1
  57. package/dist-lib/components/common/SortItems/SortItems.d.ts.map +1 -1
  58. package/dist-lib/components/common/SortItems/SortItems.js +14 -14
  59. package/dist-lib/components/common/SortItems/SortItems.js.map +1 -1
  60. package/dist-lib/components/common/SortItems/SortItems.module.scss.js +1 -1
  61. package/dist-lib/components/common/form/MultiSelect/MultiSelect.module.scss.js +16 -16
  62. package/dist-lib/components/index.d.ts +2 -0
  63. package/dist-lib/components/index.d.ts.map +1 -1
  64. package/dist-lib/components.js +199 -195
  65. package/dist-lib/components.js.map +1 -1
  66. package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
  67. package/dist-lib/hooks/usePlanSingleActions.js +22 -19
  68. package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
  69. package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js +449 -0
  70. package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js.map +1 -0
  71. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js +5219 -0
  72. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js.map +1 -0
  73. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js +1691 -0
  74. package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js.map +1 -0
  75. package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js +93 -0
  76. package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js.map +1 -0
  77. package/dist-lib/routes/Login/Login.d.ts.map +1 -1
  78. package/dist-lib/routes/Login/Login.js +45 -36
  79. package/dist-lib/routes/Login/Login.js.map +1 -1
  80. package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
  81. package/dist-lib/routes/PlanSingle/PlanSingle.js +131 -118
  82. package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
  83. package/dist-lib/routes/Plans/Plans.d.ts.map +1 -1
  84. package/dist-lib/routes/Plans/Plans.js +77 -51
  85. package/dist-lib/routes/Plans/Plans.js.map +1 -1
  86. package/dist-lib/services/plans.d.ts +33 -5
  87. package/dist-lib/services/plans.d.ts.map +1 -1
  88. package/dist-lib/services/plans.js +92 -67
  89. package/dist-lib/services/plans.js.map +1 -1
  90. package/dist-lib/services.js +93 -91
  91. package/dist-lib/styles/core-frontend.css +1 -1
  92. package/dist-lib/utils/helpers.d.ts +2 -0
  93. package/dist-lib/utils/helpers.d.ts.map +1 -1
  94. package/dist-lib/utils/helpers.js +68 -42
  95. package/dist-lib/utils/helpers.js.map +1 -1
  96. package/dist-lib/utils.js +36 -34
  97. package/package.json +3 -1
  98. package/src/@types/plans.ts +5 -0
  99. package/src/components/Plan/AddPlan/AddPlan.tsx +22 -16
  100. package/src/components/Plan/BackupProgress/BackupProgress.tsx +1 -1
  101. package/src/components/Plan/FilterPlans/FilterPlans.module.scss +65 -0
  102. package/src/components/Plan/FilterPlans/FilterPlans.tsx +126 -0
  103. package/src/components/Plan/PlanForm/PlanForm.tsx +7 -1
  104. package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +19 -0
  105. package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +40 -3
  106. package/src/components/Plan/PlanItems/PlanItem.tsx +1 -1
  107. package/src/components/Plan/PlanRepair/PlanRepair.module.scss +53 -0
  108. package/src/components/Plan/PlanRepair/PlanRepair.tsx +243 -0
  109. package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +6 -2
  110. package/src/components/Plan/PlanSettings/PlanGeneralSettings.tsx +14 -2
  111. package/src/components/Plan/PlanSettings/PlanSettings.module.scss +8 -0
  112. package/src/components/Plan/PlanSizeChart/PlanSizeChart.module.scss +76 -0
  113. package/src/components/Plan/PlanSizeChart/PlanSizeChart.tsx +163 -0
  114. package/src/components/Plan/PlanStats/PlanStats.module.scss +16 -2
  115. package/src/components/Plan/PlanStats/PlanStats.tsx +8 -11
  116. package/src/components/common/Icon/Icon.tsx +21 -0
  117. package/src/components/common/SortItems/SortItems.module.scss +3 -2
  118. package/src/components/common/SortItems/SortItems.tsx +6 -3
  119. package/src/components/common/form/MultiSelect/MultiSelect.module.scss +1 -0
  120. package/src/components/index.ts +2 -0
  121. package/src/hooks/usePlanSingleActions.tsx +26 -23
  122. package/src/routes/Login/Login.tsx +8 -2
  123. package/src/routes/PlanSingle/PlanSingle.tsx +17 -0
  124. package/src/routes/Plans/Plans.tsx +70 -35
  125. package/src/services/plans.ts +40 -4
  126. package/src/utils/helpers.ts +25 -0
@@ -0,0 +1,65 @@
1
+ .filterItems {
2
+ position: relative;
3
+ .filterBtn {
4
+ display: flex;
5
+ align-items: center;
6
+ .filterCount {
7
+ background-color: var(--primary-color);
8
+ color: var(--primary-text-color);
9
+ padding: 1px 6px;
10
+ border-radius: 12px;
11
+ font-size: 10px;
12
+ }
13
+ &.filterBtnActive {
14
+ color: var(--primary-color);
15
+ }
16
+ }
17
+ .dropdown {
18
+ margin-top: -1px;
19
+ position: absolute;
20
+ z-index: 9999;
21
+ border: 1px solid var(--line-color);
22
+ background: var(--field-bg);
23
+ box-shadow: 0 0 15px var(--content-shadow-color);
24
+ width: calc(100% - 2px);
25
+ border-radius: 0 0 4px 4px;
26
+ overflow: auto;
27
+ font-size: 0.75rem;
28
+ right: 0;
29
+ width: max-content;
30
+ max-height: 320px;
31
+ border-radius: 6px;
32
+
33
+ .field {
34
+ padding: 12px 12px 0 12px;
35
+ label {
36
+ font-weight: 600;
37
+ margin-bottom: 4px;
38
+ display: block;
39
+ }
40
+ }
41
+ .footer {
42
+ border-top: 1px solid var(--line-color);
43
+ margin-top: 12px;
44
+ display: flex;
45
+ justify-content: space-around;
46
+ button:nth-child(2) {
47
+ padding: 6px 10px;
48
+ background: var(--primary-color-light);
49
+ color: var(--primary-color);
50
+ line-height: 1em;
51
+ height: fit-content;
52
+ border-radius: 4px;
53
+ margin: 8px;
54
+ font-weight: 600;
55
+ &:hover {
56
+ background: var(--primary-color);
57
+ color: var(--primary-text-color);
58
+ }
59
+ }
60
+ button:hover {
61
+ color: var(--primary-color);
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,126 @@
1
+ import { useMemo, useState } from 'react';
2
+ import classes from './FilterPlans.module.scss';
3
+ import { Plan } from '../../..';
4
+ import { Icon, MultiSelect } from '../..';
5
+
6
+ type FilterPlansProps = {
7
+ onUpdate: (s: Record<PlanFilterTypes, string[]> | null) => void;
8
+ plans: Plan[];
9
+ };
10
+ export type PlanFilterTypes = 'devices' | 'tags' | 'storages' | 'methods';
11
+
12
+ const defaultState = { devices: [], tags: [], storages: [], methods: [] };
13
+
14
+ const FilterPlans = ({ onUpdate, plans }: FilterPlansProps) => {
15
+ const [showDropDown, setShowDropDown] = useState(false);
16
+ const [selected, setSelected] = useState<Record<PlanFilterTypes, string[]>>(() => {
17
+ const storedSettingsRaw = localStorage.getItem('plans_filter');
18
+ const storedSettings = storedSettingsRaw ? JSON.parse(storedSettingsRaw) : null;
19
+ return storedSettings || defaultState;
20
+ });
21
+
22
+ const hasFilters = Object.values(selected).some((arr) => arr.length > 0);
23
+
24
+ const filterableItems = useMemo(() => {
25
+ const items: Record<PlanFilterTypes, { label: string; value: string }[]> = { devices: [], tags: [], storages: [], methods: [] };
26
+ plans.forEach((plan) => {
27
+ plan.tags.forEach((tag) => {
28
+ if (!items.tags.find((t) => t.value === tag)) {
29
+ items.tags.push({ label: tag, value: tag });
30
+ }
31
+ });
32
+ if (!items.devices.find((t) => t.value === plan.device.id)) {
33
+ items.devices.push({ label: plan.device.name, value: plan.device.id });
34
+ }
35
+ if (!items.storages.find((t) => t.value === plan.storage.id)) {
36
+ items.storages.push({ label: plan.storage.name, value: plan.storage.id });
37
+ }
38
+ if (!items.methods.find((t) => t.value === plan.method)) {
39
+ items.methods.push({ label: plan.method, value: plan.method });
40
+ }
41
+ });
42
+ return items;
43
+ }, [plans]);
44
+
45
+ return (
46
+ <div className={classes.filterItems}>
47
+ <button
48
+ className={`${classes.filterBtn} ${showDropDown || hasFilters ? classes.filterBtnActive : ''}`}
49
+ onClick={() => setShowDropDown(!showDropDown)}
50
+ data-tooltip-id="appTooltip"
51
+ data-tooltip-content="Filter"
52
+ aria-label="Filter Plans"
53
+ data-tooltip-place="top"
54
+ data-tooltip-delay-show={500}
55
+ >
56
+ <Icon type="filter" size={18} />{' '}
57
+ {hasFilters && <span className={classes.filterCount}>{Object.values(selected).reduce((acc, arr) => acc + arr.length, 0)}</span>}
58
+ </button>
59
+ {showDropDown && (
60
+ <div className={`${classes.dropdown} styled__scrollbar`}>
61
+ {filterableItems.devices.length > 1 && (
62
+ <div className={classes.field}>
63
+ <label>Devices</label>
64
+ <MultiSelect
65
+ title="Devices"
66
+ fieldValue={selected.devices}
67
+ options={filterableItems.devices}
68
+ onUpdate={(devices) => setSelected((selected) => ({ ...selected, devices }))}
69
+ />
70
+ </div>
71
+ )}
72
+ <div className={classes.field}>
73
+ <label>Backup Type</label>
74
+ <MultiSelect
75
+ title="Backup Type"
76
+ fieldValue={selected.methods}
77
+ options={filterableItems.methods}
78
+ onUpdate={(methods) => setSelected((selected) => ({ ...selected, methods }))}
79
+ />
80
+ </div>
81
+ <div className={classes.field}>
82
+ <label>Storage</label>
83
+ <MultiSelect
84
+ title="Storages"
85
+ fieldValue={selected.storages}
86
+ options={filterableItems.storages}
87
+ onUpdate={(storages) => setSelected((selected) => ({ ...selected, storages }))}
88
+ />
89
+ </div>
90
+ <div className={classes.field}>
91
+ <label>Tags</label>
92
+ <MultiSelect
93
+ title="Tags"
94
+ fieldValue={selected.tags}
95
+ options={filterableItems.tags}
96
+ onUpdate={(tags) => setSelected((selected) => ({ ...selected, tags }))}
97
+ />
98
+ </div>
99
+ <div className={classes.footer}>
100
+ <button
101
+ onClick={() => {
102
+ localStorage.removeItem('plans_filter');
103
+ setSelected(defaultState);
104
+ onUpdate(null);
105
+ setShowDropDown(false);
106
+ }}
107
+ >
108
+ <Icon type="reload" /> Reset
109
+ </button>
110
+ <button
111
+ onClick={() => {
112
+ localStorage.setItem('plans_filter', JSON.stringify(selected));
113
+ onUpdate(selected);
114
+ setShowDropDown(false);
115
+ }}
116
+ >
117
+ <Icon type="check" size={12} /> Apply
118
+ </button>
119
+ </div>
120
+ </div>
121
+ )}
122
+ </div>
123
+ );
124
+ };
125
+
126
+ export default FilterPlans;
@@ -4,7 +4,7 @@ import SidePanel from '../../common/SidePanel/SidePanel';
4
4
  import StoragePicker from '../../common/form/StoragePicker/StoragePicker';
5
5
  import PlanStrategySettings from '../PlanSettings/PlanStrategySettings';
6
6
  import PlanSourceSettings from '../PlanSettings/PlanSourceSettings';
7
- import { NewPlanSettings } from '../../../@types/plans';
7
+ import { NewPlanSettings, PlanAddRunSettings } from '../../../@types/plans';
8
8
  import classes from '../AddPlan/AddPlan.module.scss';
9
9
  import PFClasses from './PlanForm.module.scss';
10
10
  import { useGetSettings } from '../../../services/settings';
@@ -27,6 +27,8 @@ type PlanFormProps = {
27
27
  storagePath?: string;
28
28
  storageId?: string;
29
29
  planId?: string;
30
+ runSettings?: PlanAddRunSettings;
31
+ setRunSettings?: (runSettings: PlanAddRunSettings) => void;
30
32
  };
31
33
 
32
34
  const PlanForm = ({
@@ -40,6 +42,8 @@ const PlanForm = ({
40
42
  storagePath,
41
43
  storageId,
42
44
  planId,
45
+ runSettings,
46
+ setRunSettings,
43
47
  }: PlanFormProps) => {
44
48
  const [step, setStep] = useState<number>(1);
45
49
 
@@ -279,6 +283,8 @@ const PlanForm = ({
279
283
  onUpdate={onPlanSettingsChange}
280
284
  device={deviceInstance}
281
285
  isEditing={type === 'edit'}
286
+ runSettings={runSettings}
287
+ setRunSettings={setRunSettings}
282
288
  />
283
289
  </div>
284
290
  )}
@@ -103,6 +103,25 @@
103
103
  }
104
104
  }
105
105
 
106
+ .repairBox {
107
+ border: 1px solid var(--line-color);
108
+ padding: 12px;
109
+ margin: 15px 5px;
110
+ box-sizing: border-box;
111
+ border-radius: 6px;
112
+ border-color: var(--primary-color-mid) #aaacff;
113
+ background: var(--background-color);
114
+
115
+ button {
116
+ background-color: var(--primary-color);
117
+ color: var(--primary-text-color);
118
+ padding: 6px 12px;
119
+ cursor: pointer;
120
+ border-radius: 4px;
121
+ margin-top: 6px;
122
+ }
123
+ }
124
+
106
125
  @media only screen and (max-width: 768px) {
107
126
  .integrityLogs .integrityLogsHeader {
108
127
  flex-direction: column;
@@ -13,9 +13,10 @@ interface PlanIntegrityProps {
13
13
  storage: { name: string; type: string; id: string };
14
14
  replicationStorages: PlanReplicationStorage[];
15
15
  onClose: () => void;
16
+ onRepairOpen: (replicationId: string) => void;
16
17
  }
17
18
 
18
- const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replicationStorages = [], onClose }: PlanIntegrityProps) => {
19
+ const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replicationStorages = [], onClose, onRepairOpen }: PlanIntegrityProps) => {
19
20
  const [showContent, setShowContent] = useState('primary');
20
21
  const integrityCheckMutation = useCheckPlanIntegrity();
21
22
 
@@ -67,6 +68,11 @@ const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replica
67
68
  .split('\n')
68
69
  .map((line, index) => <p key={index}>{line.includes('`') ? <code>{line.replace(/`/g, '')}</code> : line}</p>)}
69
70
  {!backupResData.fix && <p>No Suggestion for this issue.</p>}
71
+ {(backupResData.errorType === 'pack_file_error' || backupResData.errorType === 'index_error') && (
72
+ <p>
73
+ <button onClick={() => onRepairOpen(storageType)}>Open Repo Repair</button> Window for more options.
74
+ </p>
75
+ )}
70
76
  <p>
71
77
  Learn more about fixing restic repo issues{' '}
72
78
  <a
@@ -96,6 +102,37 @@ const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replica
96
102
  return <div className="label error">⛔ Error Found. {backupMessage}</div>;
97
103
  };
98
104
 
105
+ const renderRepairContent = (storageType: string) => {
106
+ const backupResData = getBackupResultByStorage(storageType);
107
+ return (
108
+ <div className={classes.repairBox}>
109
+ {(backupResData?.errorType === 'pack_file_error' || backupResData?.errorType === 'repairable_pack_file_error') && (
110
+ <div>
111
+ Some pack files in the repository are either damaged or missing and can be repaired.{' '}
112
+ <button onClick={() => onRepairOpen(storageType)}>
113
+ <Icon type="repair" size={13} /> Open Repo Repair Tool
114
+ </button>
115
+ </div>
116
+ )}
117
+ {backupResData?.errorType === 'index_error' && (
118
+ <div>
119
+ The index files in the repository are corrupted and can be repaired.{' '}
120
+ <button onClick={() => onRepairOpen(storageType)}>
121
+ <Icon type="repair" size={13} /> Open Repo Repair Tool
122
+ </button>{' '}
123
+ </div>
124
+ )}
125
+ </div>
126
+ );
127
+ };
128
+
129
+ const renderRepairOrFixSuggestion = (storageType: string) => {
130
+ const backupResData = getBackupResultByStorage(storageType);
131
+ const errorType = backupResData?.errorType;
132
+ const isRepairable = errorType === 'pack_file_error' || errorType === 'index_error' || errorType === 'repairable_pack_file_error';
133
+ return isRepairable ? renderRepairContent(storageType) : renderBackupFixSuggestion(storageType);
134
+ };
135
+
99
136
  const renderResultWithReplications = () => {
100
137
  return (
101
138
  <div className={`${classes.integrityResult} ${classes.withReplications}`}>
@@ -130,7 +167,7 @@ const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replica
130
167
  <div className={classes.replicationResultContent}>
131
168
  {renderBackupStatus(storageType)}
132
169
  {renderLogs(storageType)}
133
- {renderBackupFixSuggestion(storageType)}
170
+ {renderRepairOrFixSuggestion(storageType)}
134
171
  </div>
135
172
  )}
136
173
  </div>
@@ -152,7 +189,7 @@ const PlanIntegrity = ({ planId, taskPending, verificationData, storage, replica
152
189
  <div className={classes.integrityResult}>
153
190
  {renderBackupStatus('primary')}
154
191
  {renderLogs('primary')}
155
- {renderBackupFixSuggestion('primary')}
192
+ {renderRepairOrFixSuggestion('primary')}
156
193
  </div>
157
194
  ) : (
158
195
  renderResultWithReplications()
@@ -79,7 +79,7 @@ const PlanItem = ({ plan, layout = 'list' }: PlanItemProps) => {
79
79
  return;
80
80
  }
81
81
  toast.promise(
82
- performBackupMutation.mutateAsync(id),
82
+ performBackupMutation.mutateAsync({ id }),
83
83
  {
84
84
  pending: `Starting ${isSync ? 'Sync' : 'Backup'}...`,
85
85
  success: `${isSync ? 'Sync' : 'Backup'} initiated successfully! 🚀`,
@@ -0,0 +1,53 @@
1
+ .repairContainer {
2
+ position: relative;
3
+ small {
4
+ padding: 12px 0;
5
+ display: block;
6
+ }
7
+ .repairContent {
8
+ border: 1px solid var(--line-color);
9
+ border-radius: 6px;
10
+ button {
11
+ background: var(--background-color);
12
+ border-radius: 20px;
13
+ line-height: 1.7em;
14
+ color: var(--primary-color);
15
+ padding: 2px 10px;
16
+ transition: all 0.1s linear;
17
+ &:hover {
18
+ background: var(--primary-color);
19
+ color: var(--primary-text-color);
20
+ }
21
+ }
22
+ .repairTitle {
23
+ background: var(--background-color);
24
+ margin-top: 0;
25
+ font-weight: 600;
26
+ font-size: 1.1em;
27
+ padding: 12px;
28
+ }
29
+ p {
30
+ padding: 16px 8px;
31
+ border-bottom: 1px solid var(--line-color);
32
+ margin: 0;
33
+ }
34
+ p:last-child {
35
+ border-bottom: none;
36
+ }
37
+ }
38
+ .overlay {
39
+ position: absolute;
40
+ width: 100%;
41
+ height: 100%;
42
+ top: 0;
43
+ left: 0;
44
+ opacity: 0.95;
45
+ background-color: var(--modal-bg);
46
+ z-index: 3;
47
+ justify-content: center;
48
+ align-items: center;
49
+ display: flex;
50
+ border: 1px solid var(--line-color);
51
+ border-radius: 6px;
52
+ }
53
+ }
@@ -0,0 +1,243 @@
1
+ import { toast } from 'react-toastify';
2
+ import { usePausePlan, usePerformBackup, useRepairBackupPlan, useResumePlan } from '../../../services';
3
+ import Icon from '../../common/Icon/Icon';
4
+ import SidePanel from '../../common/SidePanel/SidePanel';
5
+ import classes from './PlanRepair.module.scss';
6
+
7
+ interface PlanRepairProps {
8
+ planId: string;
9
+ errorType: string;
10
+ onClose: () => void;
11
+ onOpenIntegrity: () => void;
12
+ }
13
+
14
+ const PlanRepair = ({ planId, errorType, onClose, onOpenIntegrity }: PlanRepairProps) => {
15
+ const pauseMutation = usePausePlan();
16
+ const resumeMutation = useResumePlan();
17
+
18
+ const performBackupMutation = usePerformBackup();
19
+ const repairRepoMutation = useRepairBackupPlan();
20
+
21
+ const shouldBeInaccessible =
22
+ pauseMutation.isPending || resumeMutation.isPending || performBackupMutation.isPending || repairRepoMutation.isPending;
23
+
24
+ const pausePlan = () => {
25
+ toast.promise(
26
+ pauseMutation.mutateAsync(planId),
27
+ {
28
+ pending: 'Pausing backup Plan...',
29
+ success: 'Backup Plan Paused',
30
+ error: {
31
+ render({ data }: any) {
32
+ return `Failed to Pause Backup Plan. ${data?.message || 'Unknown Error.'}`;
33
+ },
34
+ },
35
+ },
36
+ { autoClose: 3000 },
37
+ );
38
+ };
39
+
40
+ const resumePlan = () => {
41
+ toast.promise(
42
+ resumeMutation.mutateAsync(planId),
43
+ {
44
+ pending: 'Resuming backup Plan...',
45
+ success: 'Backup Plan Resumed',
46
+ error: {
47
+ render({ data }: any) {
48
+ return `Failed to Resume Backup Plan. ${data?.message || 'Unknown Error.'}`;
49
+ },
50
+ },
51
+ },
52
+ { autoClose: 3000 },
53
+ );
54
+ };
55
+
56
+ const runBackup = () => {
57
+ toast.promise(performBackupMutation.mutateAsync({ id: planId, runConfig: { ignoreErrors: true, skipPrune: true } }), {
58
+ pending: 'Running backup...',
59
+ success: 'Backup Started!',
60
+ error: {
61
+ render({ data }: any) {
62
+ return `Failed to Start Backup. ${data?.message || 'Unknown Error.'}`;
63
+ },
64
+ },
65
+ });
66
+ };
67
+
68
+ const repairRepo = (type: 'index' | 'snapshots' | 'packs') => {
69
+ toast.promise(repairRepoMutation.mutateAsync({ planId, type }), {
70
+ pending: `Repairing broken ${type}...`,
71
+ success: `Successfully repaired broken ${type}.`,
72
+ error: {
73
+ render({ data }: any) {
74
+ return `Failed to repair broken ${type}. ${data?.message || 'Unknown Error.'}`;
75
+ },
76
+ },
77
+ });
78
+ };
79
+
80
+ const renderMissingPackRepairContent = () => {
81
+ return (
82
+ <div className={classes.repairContent}>
83
+ <p className={classes.repairTitle}>Fixing Restic Repo with Damaged or Missing Packs</p>
84
+ <p>
85
+ <strong>Step 1: </strong> First,{' '}
86
+ <button onClick={pausePlan} disabled={shouldBeInaccessible}>
87
+ <Icon type="pause" size={12} /> Pause
88
+ </button>{' '}
89
+ the backup plan to prevent any new backup runs from starting.
90
+ </p>
91
+ <p>
92
+ <strong>Step 2: </strong> Download/Backup the index and the snapshots directories from the destination storage.
93
+ </p>
94
+ <p>
95
+ <strong>Step 3: </strong> Then{' '}
96
+ <button onClick={() => repairRepo('index')} disabled={shouldBeInaccessible}>
97
+ <Icon type="repair" size={12} /> Repair the Repo Index
98
+ </button>{' '}
99
+ to fix missing or damaged pack files.
100
+ </p>
101
+ <p>
102
+ <strong>Step 4: </strong> Then{' '}
103
+ <button onClick={runBackup} disabled={shouldBeInaccessible}>
104
+ <Icon type="backup" size={13} /> Run a Backup
105
+ </button>{' '}
106
+ to update the Repo Index after repairing it.
107
+ </p>
108
+ <p>
109
+ <strong>Step 5: </strong> Then{' '}
110
+ <button onClick={onOpenIntegrity} disabled={shouldBeInaccessible}>
111
+ <Icon type="integrity" size={12} /> Check Integrity
112
+ </button>{' '}
113
+ again to see if the repo is fixed.
114
+ </p>
115
+ <p>
116
+ <strong>Step 6: </strong> If that did not fix the issue,{' '}
117
+ <button onClick={() => repairRepo('snapshots')} disabled={shouldBeInaccessible}>
118
+ <Icon type="repair" size={12} /> Repair Broken Snapshots
119
+ </button>{' '}
120
+ and then{' '}
121
+ <button onClick={onOpenIntegrity} disabled={shouldBeInaccessible}>
122
+ <Icon type="integrity" size={12} /> Check Integrity
123
+ </button>
124
+ </p>
125
+ <p>
126
+ <strong>Step 7: </strong> If the issue is resolved,{' '}
127
+ <button onClick={resumePlan} disabled={shouldBeInaccessible}>
128
+ <Icon type="play" size={12} /> Resume
129
+ </button>{' '}
130
+ the backup plan.
131
+ </p>
132
+ </div>
133
+ );
134
+ };
135
+ const renderPackRepairContent = () => {
136
+ return (
137
+ <div className={classes.repairContent}>
138
+ <p className={classes.repairTitle}>Fixing Restic Repo with Damaged Pack files</p>
139
+ <p>
140
+ <strong>Step 1: </strong> First,{' '}
141
+ <button onClick={pausePlan} disabled={shouldBeInaccessible}>
142
+ <Icon type="pause" size={12} /> Pause
143
+ </button>{' '}
144
+ the backup plan to prevent any new backup runs from starting.
145
+ </p>
146
+ <p>
147
+ <strong>Step 2: </strong> Then{' '}
148
+ <button onClick={() => repairRepo('packs')} disabled={shouldBeInaccessible}>
149
+ <Icon type="repair" size={12} /> Repair the Pack Files
150
+ </button>{' '}
151
+ to fix missing or damaged pack files.
152
+ </p>
153
+ <p>
154
+ <strong>Step 3: </strong> Then{' '}
155
+ <button onClick={() => repairRepo('snapshots')} disabled={shouldBeInaccessible}>
156
+ <Icon type="repair" size={12} /> Repair the Snapshots
157
+ </button>{' '}
158
+ to fix the snapshots that relied on the broken pack files.
159
+ </p>
160
+ <p>
161
+ <strong>Step 4: </strong> Then{' '}
162
+ <button onClick={onOpenIntegrity} disabled={shouldBeInaccessible}>
163
+ <Icon type="integrity" size={12} /> Check Integrity
164
+ </button>{' '}
165
+ again to see if the repo is fixed.
166
+ </p>
167
+ <p>
168
+ <strong>Step 5: </strong> If the issue is resolved,{' '}
169
+ <button onClick={resumePlan} disabled={shouldBeInaccessible}>
170
+ <Icon type="play" size={12} /> Resume
171
+ </button>{' '}
172
+ the backup plan.
173
+ </p>
174
+ </div>
175
+ );
176
+ };
177
+
178
+ const renderIndexRepairContent = () => {
179
+ return (
180
+ <div className={classes.repairContent}>
181
+ <p className={classes.repairTitle}>Fixing Restic Repo with Damaged Index</p>
182
+ <p>
183
+ <strong>Step 1: </strong> First,{' '}
184
+ <button onClick={pausePlan} disabled={shouldBeInaccessible}>
185
+ <Icon type="pause" size={12} /> Pause
186
+ </button>{' '}
187
+ the backup plan to prevent any new backup runs from starting.
188
+ </p>
189
+ <p>
190
+ <strong>Step 2: </strong> Then{' '}
191
+ <button onClick={() => repairRepo('index')} disabled={shouldBeInaccessible}>
192
+ <Icon type="repair" size={12} /> Repair the Index
193
+ </button>{' '}
194
+ to fix damaged index files.
195
+ </p>
196
+ <p>
197
+ <strong>Step 3: </strong> Then{' '}
198
+ <button onClick={onOpenIntegrity} disabled={shouldBeInaccessible}>
199
+ <Icon type="integrity" size={12} /> Check Integrity
200
+ </button>{' '}
201
+ again to see if the repo is fixed.
202
+ </p>
203
+ <p>
204
+ <strong>Step 4: </strong> If the issue is resolved,{' '}
205
+ <button onClick={resumePlan} disabled={shouldBeInaccessible}>
206
+ <Icon type="play" size={12} /> Resume
207
+ </button>{' '}
208
+ the backup plan.
209
+ </p>
210
+ </div>
211
+ );
212
+ };
213
+
214
+ return (
215
+ <SidePanel
216
+ title="Check Backup Integrity"
217
+ icon={'integrity'}
218
+ // errorMessage={integrityCheckMutation.error?.message}
219
+ close={() => onClose()}
220
+ width="800px"
221
+ >
222
+ <div className={classes.repairContainer}>
223
+ {shouldBeInaccessible && (
224
+ <div className={classes.overlay}>
225
+ <Icon type="loading" size={36} />
226
+ </div>
227
+ )}
228
+ {errorType === 'pack_file_error' && renderMissingPackRepairContent()}
229
+ {errorType === 'repairable_pack_file_error' && renderPackRepairContent()}
230
+ {errorType === 'index_error' && renderIndexRepairContent()}
231
+ <small>
232
+ If performing the above actions does not resolve the issue, please follow this{' '}
233
+ <a href="https://restic.readthedocs.io/en/stable/077_troubleshooting.html" target="_blank" rel="noopener noreferrer">
234
+ Restic Troubleshooting Guide
235
+ </a>
236
+ .
237
+ </small>
238
+ </div>
239
+ </SidePanel>
240
+ );
241
+ };
242
+
243
+ export default PlanRepair;
@@ -3,7 +3,7 @@ import Icon from '../../common/Icon/Icon';
3
3
  import classes from './PlanSettings.module.scss';
4
4
  import PlanNotificationSettings from './PlanNotificationSettings';
5
5
  import PlanPerformanceSettings from './PlanPerformanceSettings';
6
- import { NewPlanSettings } from '../../../@types/plans';
6
+ import { NewPlanSettings, PlanAddRunSettings } from '../../../@types/plans';
7
7
  import TagsInput from '../../common/form/TagsInput/TagsInput';
8
8
  import PlanGeneralSettings from './PlanGeneralSettings';
9
9
  import { isMobile } from '../../../utils/helpers';
@@ -17,9 +17,11 @@ interface PlanAdvancedSettingsProps {
17
17
  device: Device;
18
18
  onUpdate: (notificationSettings: NewPlanSettings) => void;
19
19
  isEditing: boolean;
20
+ runSettings?: PlanAddRunSettings;
21
+ setRunSettings?: (runSettings: PlanAddRunSettings) => void;
20
22
  }
21
23
 
22
- const PlanAdvancedSettings = ({ plan, appSettings, device, onUpdate, isEditing }: PlanAdvancedSettingsProps) => {
24
+ const PlanAdvancedSettings = ({ plan, appSettings, device, onUpdate, isEditing, runSettings, setRunSettings }: PlanAdvancedSettingsProps) => {
23
25
  const [advancedTab, setAdvancedTab] = useState('General');
24
26
  const settings = plan.settings;
25
27
  const integrationTypes = useMemo(() => {
@@ -80,6 +82,8 @@ const PlanAdvancedSettings = ({ plan, appSettings, device, onUpdate, isEditing }
80
82
  settings={settings}
81
83
  onUpdate={(newSettings) => onUpdate({ ...plan, settings: newSettings })}
82
84
  isEditing={isEditing}
85
+ runSettings={runSettings}
86
+ setRunSettings={setRunSettings}
83
87
  />
84
88
  )}
85
89
  {advancedTab === 'Notification' && (