@plutonhq/core-frontend 0.1.31 → 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.
- package/dist-lib/@types/plans.d.ts +4 -0
- package/dist-lib/@types/plans.d.ts.map +1 -1
- package/dist-lib/components/Plan/AddPlan/AddPlan.d.ts.map +1 -1
- package/dist-lib/components/Plan/AddPlan/AddPlan.js +29 -24
- package/dist-lib/components/Plan/AddPlan/AddPlan.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js +33 -33
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js +36 -36
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.d.ts.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +63 -53
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js +32 -32
- package/dist-lib/components/Plan/Backups/Backups.d.ts.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.js +148 -144
- package/dist-lib/components/Plan/Backups/Backups.js.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.module.scss.js +34 -32
- package/dist-lib/components/Plan/Backups/Backups.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts +9 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts.map +1 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.js +117 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.js.map +1 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js +20 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +4 -2
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanForm/PlanForm.js +33 -29
- package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +2 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +85 -57
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +11 -9
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.js +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts +9 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.js +262 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.js.map +1 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js +14 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts +4 -2
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +24 -22
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts +4 -2
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js +39 -28
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +66 -64
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts +7 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js +116 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js.map +1 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js +20 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanStats/PlanStats.js +29 -30
- package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
- package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +16 -14
- package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js.map +1 -1
- package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
- package/dist-lib/components/common/Icon/Icon.js +395 -378
- package/dist-lib/components/common/Icon/Icon.js.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.d.ts +2 -1
- package/dist-lib/components/common/SortItems/SortItems.d.ts.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.js +14 -14
- package/dist-lib/components/common/SortItems/SortItems.js.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.module.scss.js +1 -1
- package/dist-lib/components/common/form/MultiSelect/MultiSelect.module.scss.js +16 -16
- package/dist-lib/components/index.d.ts +2 -0
- package/dist-lib/components/index.d.ts.map +1 -1
- package/dist-lib/components.js +199 -195
- package/dist-lib/components.js.map +1 -1
- package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
- package/dist-lib/hooks/usePlanSingleActions.js +22 -19
- package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
- package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js +449 -0
- package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js.map +1 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js +5219 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js.map +1 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js +1691 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js.map +1 -0
- 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
- 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
- package/dist-lib/routes/Login/Login.d.ts.map +1 -1
- package/dist-lib/routes/Login/Login.js +45 -36
- package/dist-lib/routes/Login/Login.js.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.js +131 -118
- package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
- package/dist-lib/routes/Plans/Plans.d.ts.map +1 -1
- package/dist-lib/routes/Plans/Plans.js +77 -51
- package/dist-lib/routes/Plans/Plans.js.map +1 -1
- package/dist-lib/services/plans.d.ts +33 -5
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +92 -67
- package/dist-lib/services/plans.js.map +1 -1
- package/dist-lib/services.js +93 -91
- package/dist-lib/styles/core-frontend.css +1 -1
- package/dist-lib/styles/global.scss +4 -0
- package/dist-lib/utils/helpers.d.ts +2 -0
- package/dist-lib/utils/helpers.d.ts.map +1 -1
- package/dist-lib/utils/helpers.js +68 -42
- package/dist-lib/utils/helpers.js.map +1 -1
- package/dist-lib/utils.js +36 -34
- package/package.json +3 -1
- package/src/@types/plans.ts +5 -0
- package/src/components/Plan/AddPlan/AddPlan.tsx +22 -16
- package/src/components/Plan/BackupEvents/BackupEvents.module.scss +2 -0
- package/src/components/Plan/BackupEvents/BackupEvents.tsx +2 -2
- package/src/components/Plan/BackupProgress/BackupProgress.module.scss +1 -0
- package/src/components/Plan/BackupProgress/BackupProgress.tsx +7 -2
- package/src/components/Plan/Backups/Backups.module.scss +16 -0
- package/src/components/Plan/Backups/Backups.tsx +13 -2
- package/src/components/Plan/FilterPlans/FilterPlans.module.scss +65 -0
- package/src/components/Plan/FilterPlans/FilterPlans.tsx +126 -0
- package/src/components/Plan/PlanForm/PlanForm.tsx +7 -1
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +19 -0
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +40 -3
- package/src/components/Plan/PlanItems/PlanItem.tsx +1 -1
- package/src/components/Plan/PlanRepair/PlanRepair.module.scss +53 -0
- package/src/components/Plan/PlanRepair/PlanRepair.tsx +243 -0
- package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +6 -2
- package/src/components/Plan/PlanSettings/PlanGeneralSettings.tsx +14 -2
- package/src/components/Plan/PlanSettings/PlanSettings.module.scss +8 -0
- package/src/components/Plan/PlanSizeChart/PlanSizeChart.module.scss +76 -0
- package/src/components/Plan/PlanSizeChart/PlanSizeChart.tsx +163 -0
- package/src/components/Plan/PlanStats/PlanStats.module.scss +16 -2
- package/src/components/Plan/PlanStats/PlanStats.tsx +8 -11
- package/src/components/common/Icon/Icon.tsx +21 -0
- package/src/components/common/SortItems/SortItems.module.scss +3 -2
- package/src/components/common/SortItems/SortItems.tsx +6 -3
- package/src/components/common/form/MultiSelect/MultiSelect.module.scss +1 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/usePlanSingleActions.tsx +26 -23
- package/src/routes/Login/Login.tsx +8 -2
- package/src/routes/PlanSingle/PlanSingle.tsx +17 -0
- package/src/routes/Plans/Plans.tsx +70 -35
- package/src/services/plans.ts +40 -4
- package/src/styles/global.scss +4 -0
- package/src/utils/helpers.ts +25 -0
|
@@ -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' && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NewPlanSettings } from '../../../@types/plans';
|
|
1
|
+
import { NewPlanSettings, PlanAddRunSettings } from '../../../@types/plans';
|
|
2
2
|
import NumberInput from '../../common/form/NumberInput/NumberInput';
|
|
3
3
|
import Toggle from '../../common/form/Toggle/Toggle';
|
|
4
4
|
import classes from './PlanSettings.module.scss';
|
|
@@ -7,9 +7,11 @@ interface PlanGeneralSettingsProps {
|
|
|
7
7
|
settings: NewPlanSettings['settings'];
|
|
8
8
|
onUpdate: (settings: NewPlanSettings['settings']) => void;
|
|
9
9
|
isEditing: boolean;
|
|
10
|
+
runSettings?: PlanAddRunSettings;
|
|
11
|
+
setRunSettings?: (runSettings: PlanAddRunSettings) => void;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const PlanGeneralSettings = ({ settings, onUpdate, isEditing }: PlanGeneralSettingsProps) => {
|
|
14
|
+
const PlanGeneralSettings = ({ settings, onUpdate, isEditing, runSettings, setRunSettings }: PlanGeneralSettingsProps) => {
|
|
13
15
|
const { encryption, compression, retries, retryDelay } = settings;
|
|
14
16
|
return (
|
|
15
17
|
<>
|
|
@@ -48,6 +50,16 @@ const PlanGeneralSettings = ({ settings, onUpdate, isEditing }: PlanGeneralSetti
|
|
|
48
50
|
inline={false}
|
|
49
51
|
/>
|
|
50
52
|
</div>
|
|
53
|
+
{!isEditing && setRunSettings && (
|
|
54
|
+
<div className={`${classes.field} ${classes.runNowField}`}>
|
|
55
|
+
<label className={classes.label}>Run Backup Now</label>
|
|
56
|
+
<Toggle
|
|
57
|
+
fieldValue={runSettings?.runNow ?? true}
|
|
58
|
+
onUpdate={(val: boolean) => setRunSettings && setRunSettings({ ...runSettings, runNow: val })}
|
|
59
|
+
description={'Run backup immediately after creating the plan'}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
51
63
|
</>
|
|
52
64
|
);
|
|
53
65
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.chartWrap {
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 100%;
|
|
5
|
+
min-height: 60px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.chartCanvas {
|
|
9
|
+
position: absolute;
|
|
10
|
+
inset: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.rangeSelector {
|
|
14
|
+
position: absolute;
|
|
15
|
+
top: -28px;
|
|
16
|
+
right: 0;
|
|
17
|
+
z-index: 2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.rangeBtn {
|
|
21
|
+
background: var(--content-background-color);
|
|
22
|
+
color: var(--content-text-color-light);
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
font-size: 0.6875rem;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
padding: 2px 8px;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
line-height: 1.4;
|
|
29
|
+
|
|
30
|
+
&:hover {
|
|
31
|
+
color: var(--primary-color);
|
|
32
|
+
border-color: var(--primary-color);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rangeMenu {
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: calc(100% + 4px);
|
|
39
|
+
right: 0;
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 4px 0;
|
|
42
|
+
list-style: none;
|
|
43
|
+
background: var(--content-background-color);
|
|
44
|
+
border: 1px solid var(--line-color);
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
47
|
+
min-width: 70px;
|
|
48
|
+
|
|
49
|
+
li {
|
|
50
|
+
padding: 4px 10px;
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
color: var(--content-text-color);
|
|
54
|
+
|
|
55
|
+
&:hover {
|
|
56
|
+
background: var(--primary-color-light);
|
|
57
|
+
color: var(--primary-color);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&.active {
|
|
61
|
+
color: var(--primary-color);
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.empty {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
gap: 6px;
|
|
72
|
+
height: 100%;
|
|
73
|
+
font-size: 0.75rem;
|
|
74
|
+
color: var(--content-text-color-light);
|
|
75
|
+
opacity: 0.7;
|
|
76
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useMemo, useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Filler, Tooltip, Legend, ChartOptions } from 'chart.js';
|
|
3
|
+
import { Line } from 'react-chartjs-2';
|
|
4
|
+
import Icon from '../../common/Icon/Icon';
|
|
5
|
+
import { Backup } from '../../../@types/backups';
|
|
6
|
+
import { formatBytes, formatNumberToK, isDarkMode } from '../../../utils/helpers';
|
|
7
|
+
import classes from './PlanSizeChart.module.scss';
|
|
8
|
+
|
|
9
|
+
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Filler, Tooltip, Legend);
|
|
10
|
+
|
|
11
|
+
type RangeKey = '7d' | '14d' | '1m' | '3m' | '6m';
|
|
12
|
+
|
|
13
|
+
const RANGE_OPTIONS: { key: RangeKey; label: string; days: number }[] = [
|
|
14
|
+
{ key: '7d', label: '7d', days: 7 },
|
|
15
|
+
{ key: '14d', label: '14d', days: 14 },
|
|
16
|
+
{ key: '1m', label: '1m', days: 30 },
|
|
17
|
+
{ key: '3m', label: '3m', days: 90 },
|
|
18
|
+
{ key: '6m', label: '6m', days: 180 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
interface PlanSizeChartProps {
|
|
22
|
+
backups: Backup[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PlanSizeChart = ({ backups }: PlanSizeChartProps) => {
|
|
26
|
+
const [range, setRange] = useState<RangeKey>('3m');
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!open) return;
|
|
32
|
+
const onClick = (e: MouseEvent) => {
|
|
33
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
34
|
+
setOpen(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
document.addEventListener('mousedown', onClick);
|
|
38
|
+
return () => document.removeEventListener('mousedown', onClick);
|
|
39
|
+
}, [open]);
|
|
40
|
+
|
|
41
|
+
const activeRange = RANGE_OPTIONS.find((r) => r.key === range) || RANGE_OPTIONS[3];
|
|
42
|
+
|
|
43
|
+
const filtered = useMemo(() => {
|
|
44
|
+
const cutoff = Date.now() - activeRange.days * 24 * 60 * 60 * 1000;
|
|
45
|
+
return [...(backups || [])]
|
|
46
|
+
.filter((b) => {
|
|
47
|
+
const t = new Date(b.started).getTime();
|
|
48
|
+
return !isNaN(t) && t >= cutoff;
|
|
49
|
+
})
|
|
50
|
+
.sort((a, b) => new Date(a.started).getTime() - new Date(b.started).getTime());
|
|
51
|
+
}, [backups, activeRange.days]);
|
|
52
|
+
|
|
53
|
+
const labels = filtered.map((b) => new Date(b.started).toLocaleString());
|
|
54
|
+
const sizeData = filtered.map((b) => b.totalSize || 0);
|
|
55
|
+
const filesData = filtered.map((b) => b.totalFiles || 0);
|
|
56
|
+
|
|
57
|
+
const data = {
|
|
58
|
+
labels,
|
|
59
|
+
datasets: [
|
|
60
|
+
{
|
|
61
|
+
label: 'Size',
|
|
62
|
+
data: sizeData,
|
|
63
|
+
yAxisID: 'ySize',
|
|
64
|
+
borderColor: 'transparent',
|
|
65
|
+
backgroundColor: 'rgba(87, 132, 255, 0.12)',
|
|
66
|
+
fill: true,
|
|
67
|
+
tension: 0.4,
|
|
68
|
+
borderWidth: 1.5,
|
|
69
|
+
pointRadius: 0,
|
|
70
|
+
pointHoverRadius: 4,
|
|
71
|
+
pointHoverBackgroundColor: 'rgba(87, 90, 255, 1)',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Files',
|
|
75
|
+
data: filesData,
|
|
76
|
+
yAxisID: 'yFiles',
|
|
77
|
+
borderColor: '#9a9bff',
|
|
78
|
+
backgroundColor: 'transparent',
|
|
79
|
+
borderDash: [3, 3],
|
|
80
|
+
fill: false,
|
|
81
|
+
tension: 0.4,
|
|
82
|
+
borderWidth: 1.2,
|
|
83
|
+
pointRadius: 0,
|
|
84
|
+
pointHoverRadius: 4,
|
|
85
|
+
pointHoverBackgroundColor: 'rgba(87, 90, 255, 1)',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const options: ChartOptions<'line'> = {
|
|
91
|
+
responsive: true,
|
|
92
|
+
animation: false,
|
|
93
|
+
maintainAspectRatio: false,
|
|
94
|
+
interaction: { mode: 'index', intersect: false },
|
|
95
|
+
plugins: {
|
|
96
|
+
legend: { display: false },
|
|
97
|
+
tooltip: {
|
|
98
|
+
displayColors: false,
|
|
99
|
+
backgroundColor: isDarkMode ? 'rgba(0, 0, 0, 0.9)' : 'rgba(255, 255, 255, 1)',
|
|
100
|
+
titleColor: isDarkMode ? '#fff' : '#666',
|
|
101
|
+
bodyColor: isDarkMode ? '#ccc' : '#888',
|
|
102
|
+
padding: 8,
|
|
103
|
+
titleFont: { size: 11 },
|
|
104
|
+
bodyFont: { size: 11 },
|
|
105
|
+
callbacks: {
|
|
106
|
+
title: (items) => {
|
|
107
|
+
const idx = items[0]?.dataIndex ?? 0;
|
|
108
|
+
const b = filtered[idx];
|
|
109
|
+
return b ? new Date(b.started).toLocaleString() : '';
|
|
110
|
+
},
|
|
111
|
+
label: (ctx) => {
|
|
112
|
+
if (ctx.dataset.label === 'Size') return `Size: ${formatBytes(ctx.parsed.y || 0)}`;
|
|
113
|
+
return `Files: ${formatNumberToK(ctx.parsed.y || 0)}`;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
scales: {
|
|
119
|
+
x: { display: false },
|
|
120
|
+
ySize: { display: false, beginAtZero: true },
|
|
121
|
+
yFiles: { display: false, beginAtZero: true, position: 'right' },
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className={classes.chartWrap}>
|
|
127
|
+
<div className={classes.rangeSelector} ref={dropdownRef}>
|
|
128
|
+
<button type="button" className={classes.rangeBtn} onClick={() => setOpen((v) => !v)}>
|
|
129
|
+
{activeRange.label}
|
|
130
|
+
</button>
|
|
131
|
+
{open && (
|
|
132
|
+
<ul className={classes.rangeMenu}>
|
|
133
|
+
{RANGE_OPTIONS.slice()
|
|
134
|
+
.reverse()
|
|
135
|
+
.map((opt) => (
|
|
136
|
+
<li
|
|
137
|
+
key={opt.key}
|
|
138
|
+
className={opt.key === range ? classes.active : ''}
|
|
139
|
+
onClick={() => {
|
|
140
|
+
setRange(opt.key);
|
|
141
|
+
setOpen(false);
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
{opt.label}
|
|
145
|
+
</li>
|
|
146
|
+
))}
|
|
147
|
+
</ul>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
{filtered.length === 0 ? (
|
|
151
|
+
<div className={classes.empty}>
|
|
152
|
+
<Icon type="folders" size={16} /> No data in range
|
|
153
|
+
</div>
|
|
154
|
+
) : (
|
|
155
|
+
<div className={classes.chartCanvas}>
|
|
156
|
+
<Line data={data} options={options} />
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default PlanSizeChart;
|
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
span {
|
|
17
17
|
color: var(--icon-color);
|
|
18
18
|
}
|
|
19
|
+
.widgetSubTitle {
|
|
20
|
+
margin-left: 4px;
|
|
21
|
+
i {
|
|
22
|
+
font-style: normal;
|
|
23
|
+
font-family: sans-serif;
|
|
24
|
+
font-size: 9px;
|
|
25
|
+
padding: 0 2px;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
.sources,
|
|
@@ -83,12 +92,17 @@
|
|
|
83
92
|
}
|
|
84
93
|
.snapshots {
|
|
85
94
|
width: 24%;
|
|
95
|
+
padding: 20px 0 0 0;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
justify-content: space-between;
|
|
86
99
|
.snapshotsContent {
|
|
87
100
|
display: flex;
|
|
88
101
|
justify-content: space-around;
|
|
89
102
|
align-items: center;
|
|
90
|
-
height:
|
|
91
|
-
margin-top:
|
|
103
|
+
height: 70px;
|
|
104
|
+
margin-top: 16px;
|
|
105
|
+
position: relative;
|
|
92
106
|
|
|
93
107
|
& > div {
|
|
94
108
|
text-align: center;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Icon from '../../common/Icon/Icon';
|
|
2
2
|
import { Plan } from '../../../@types/plans';
|
|
3
|
-
import {
|
|
3
|
+
import { formatIntervalDisplay } from '../../../utils/helpers';
|
|
4
4
|
import PlanHistory from '../PlanHistory/PlanHistory';
|
|
5
|
+
import PlanSizeChart from '../PlanSizeChart/PlanSizeChart';
|
|
5
6
|
import classes from './PlanStats.module.scss';
|
|
6
7
|
import PlanStorageInfo from '../PlanStorageInfo/PlanStorageInfo';
|
|
8
|
+
import { formatBytes, formatNumberToK } from '../../../utils/helpers';
|
|
7
9
|
|
|
8
10
|
interface PlanStatsProps {
|
|
9
11
|
plan: Plan;
|
|
@@ -61,18 +63,13 @@ const PlanStats = ({ plan, isSync, lastBackupItem }: PlanStatsProps) => {
|
|
|
61
63
|
</div>
|
|
62
64
|
<div className={classes.snapshots}>
|
|
63
65
|
<div className={classes.widgetTitle}>
|
|
64
|
-
<Icon type="folders" size={12} />
|
|
66
|
+
<Icon type="folders" size={12} /> Stats{' '}
|
|
67
|
+
<span className={classes.widgetSubTitle}>
|
|
68
|
+
{formatNumberToK(totalFiles)} files <i>|</i> {formatBytes(totalSize)}
|
|
69
|
+
</span>
|
|
65
70
|
</div>
|
|
66
71
|
<div className={classes.snapshotsContent}>
|
|
67
|
-
<
|
|
68
|
-
<span>{totalFiles ? formatNumberToK(totalFiles) : 0}</span>
|
|
69
|
-
<span>Files</span>
|
|
70
|
-
</div>
|
|
71
|
-
<div></div>
|
|
72
|
-
<div>
|
|
73
|
-
<span>{totalSize ? formatBytes(totalSize) : '0.00B'}</span>
|
|
74
|
-
<span>Size</span>
|
|
75
|
-
</div>
|
|
72
|
+
<PlanSizeChart backups={plan.backups} />
|
|
76
73
|
</div>
|
|
77
74
|
</div>
|
|
78
75
|
<div className={classes.health}>
|
|
@@ -978,6 +978,18 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
|
|
|
978
978
|
></path>
|
|
979
979
|
</IconWrapper>
|
|
980
980
|
)}
|
|
981
|
+
{type === 'repair' && (
|
|
982
|
+
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
983
|
+
<g fill="none" fillRule="evenodd">
|
|
984
|
+
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path>
|
|
985
|
+
<path
|
|
986
|
+
fill={color}
|
|
987
|
+
d="M7.527 2.657a7.001 7.001 0 0 1 8.26 9.347l4.599 3.893a3.3 3.3 0 1 1-4.651 4.65l-3.891-4.597a7.001 7.001 0 0 1-9.35-8.26a1.01 1.01 0 0 1 1.72-.432l3.045 3.307l2.297-.845l.847-2.3l-3.309-3.04a1.01 1.01 0 0 1 .433-1.723"
|
|
988
|
+
></path>
|
|
989
|
+
</g>
|
|
990
|
+
</IconWrapper>
|
|
991
|
+
)}
|
|
992
|
+
|
|
981
993
|
{type === 'dots-vertical' && (
|
|
982
994
|
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
983
995
|
<path
|
|
@@ -1151,6 +1163,15 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
|
|
|
1151
1163
|
></path>
|
|
1152
1164
|
</IconWrapper>
|
|
1153
1165
|
)}
|
|
1166
|
+
{type === 'filter' && (
|
|
1167
|
+
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
1168
|
+
<path
|
|
1169
|
+
fill={color}
|
|
1170
|
+
d="M15 19.88c.04.3-.06.62-.29.83a.996.996 0 0 1-1.41 0L9.29 16.7a.99.99 0 0 1-.29-.83v-5.12L4.21 4.62a1 1 0 0 1 .17-1.4c.19-.14.4-.22.62-.22h14c.22 0 .43.08.62.22a1 1 0 0 1 .17 1.4L15 10.75zM7.04 5L11 10.06v5.52l2 2v-7.53L16.96 5z"
|
|
1171
|
+
></path>
|
|
1172
|
+
</IconWrapper>
|
|
1173
|
+
)}
|
|
1174
|
+
|
|
1154
1175
|
{type === 'notification' && (
|
|
1155
1176
|
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
1156
1177
|
<g fill="none" stroke={color} strokeWidth={1.5}>
|