@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.
- package/dist-lib/@types/backups.d.ts +26 -0
- package/dist-lib/@types/backups.d.ts.map +1 -1
- package/dist-lib/@types/devices.d.ts +7 -0
- package/dist-lib/@types/devices.d.ts.map +1 -1
- package/dist-lib/@types/plans.d.ts +21 -1
- package/dist-lib/@types/plans.d.ts.map +1 -1
- package/dist-lib/@types/restores.d.ts +2 -0
- package/dist-lib/@types/restores.d.ts.map +1 -1
- package/dist-lib/components/Device/DeviceBackups/DeviceBackups.d.ts +3 -2
- package/dist-lib/components/Device/DeviceBackups/DeviceBackups.d.ts.map +1 -1
- package/dist-lib/components/Device/DeviceBackups/DeviceBackups.js +73 -85
- package/dist-lib/components/Device/DeviceBackups/DeviceBackups.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.d.ts.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js +88 -50
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js +70 -38
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.d.ts.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +166 -123
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js +64 -30
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.d.ts +8 -1
- package/dist-lib/components/Plan/Backups/Backups.d.ts.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.js +154 -125
- package/dist-lib/components/Plan/Backups/Backups.js.map +1 -1
- package/dist-lib/components/Plan/EditPlan/EditPlan.d.ts.map +1 -1
- package/dist-lib/components/Plan/EditPlan/EditPlan.js +11 -10
- package/dist-lib/components/Plan/EditPlan/EditPlan.js.map +1 -1
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.d.ts +12 -0
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.d.ts.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.js +68 -0
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.module.scss.js +26 -0
- package/dist-lib/components/Plan/Mirrors/MirrorDetails.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.d.ts +11 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.d.ts.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.js +38 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.module.scss.js +16 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStatusBadge.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.d.ts +14 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.d.ts.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.js +54 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.module.scss.js +26 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelector.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.d.ts +15 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.d.ts.map +1 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.js +34 -0
- package/dist-lib/components/Plan/Mirrors/MirrorStorageSelectorModal.js.map +1 -0
- package/dist-lib/components/Plan/PlanBackups/PlanBackups.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanBackups/PlanBackups.js +20 -17
- package/dist-lib/components/Plan/PlanBackups/PlanBackups.js.map +1 -1
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +2 -1
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanForm/PlanForm.js +85 -58
- package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.js +58 -59
- package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
- package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js +8 -8
- package/dist-lib/components/Plan/PlanRemoveModal/PlanRemoveModal.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts +14 -0
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js +290 -0
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.js.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.module.scss.js +26 -0
- package/dist-lib/components/Plan/PlanSettings/PlanReplicationSettings.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 +41 -42
- package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
- package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +5 -5
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts +15 -0
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js +69 -0
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.js.map +1 -0
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js +16 -0
- package/dist-lib/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss.js.map +1 -0
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js +36 -34
- package/dist-lib/components/Restore/RestoreWizard/RestoreConfirmStep.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js +7 -5
- package/dist-lib/components/Restore/RestoreWizard/RestorePreviewStep.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts +12 -4
- package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js +44 -32
- package/dist-lib/components/Restore/RestoreWizard/RestoreSettingsStep.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts +5 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.d.ts.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js +48 -44
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.js.map +1 -1
- package/dist-lib/components/Restore/RestoreWizard/RestoreWizard.module.scss.js +32 -32
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +14 -14
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js.map +1 -1
- package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.d.ts.map +1 -1
- package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js +28 -19
- package/dist-lib/components/Settings/IntegrationSettings/IntegrationSettings.js.map +1 -1
- package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
- package/dist-lib/components/common/Icon/Icon.js +11 -0
- package/dist-lib/components/common/Icon/Icon.js.map +1 -1
- package/dist-lib/components/common/PageHeader/PageHeader.module.scss.js +6 -6
- package/dist-lib/components/index.d.ts +4 -0
- package/dist-lib/components/index.d.ts.map +1 -1
- package/dist-lib/components.js +86 -78
- package/dist-lib/components.js.map +1 -1
- package/dist-lib/hooks/usePwaAutoUpdate.d.ts +11 -2
- package/dist-lib/hooks/usePwaAutoUpdate.d.ts.map +1 -1
- package/dist-lib/hooks/usePwaAutoUpdate.js +32 -10
- package/dist-lib/hooks/usePwaAutoUpdate.js.map +1 -1
- package/dist-lib/router.d.ts.map +1 -1
- package/dist-lib/router.js +46 -35
- package/dist-lib/router.js.map +1 -1
- package/dist-lib/routes/DeviceSingle/DeviceSingle.d.ts.map +1 -1
- package/dist-lib/routes/DeviceSingle/DeviceSingle.js +40 -40
- package/dist-lib/routes/DeviceSingle/DeviceSingle.js.map +1 -1
- package/dist-lib/services/backups.d.ts +15 -2
- package/dist-lib/services/backups.d.ts.map +1 -1
- package/dist-lib/services/backups.js +119 -100
- package/dist-lib/services/backups.js.map +1 -1
- package/dist-lib/services/plans.d.ts +14 -0
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +160 -129
- package/dist-lib/services/plans.js.map +1 -1
- package/dist-lib/services/restores.d.ts +10 -2
- package/dist-lib/services/restores.d.ts.map +1 -1
- package/dist-lib/services/restores.js +61 -57
- package/dist-lib/services/restores.js.map +1 -1
- package/dist-lib/services/users.d.ts.map +1 -1
- package/dist-lib/services/users.js +32 -32
- package/dist-lib/services/users.js.map +1 -1
- package/dist-lib/services.js +107 -103
- package/dist-lib/styles/core-frontend.css +1 -1
- package/dist-lib/utils/progressHelpers.d.ts +12 -1
- package/dist-lib/utils/progressHelpers.d.ts.map +1 -1
- package/dist-lib/utils/progressHelpers.js +121 -63
- package/dist-lib/utils/progressHelpers.js.map +1 -1
- package/dist-lib/utils.js +29 -28
- package/package.json +1 -1
- package/src/@types/backups.ts +28 -0
- package/src/@types/devices.ts +8 -0
- package/src/@types/plans.ts +23 -1
- package/src/@types/restores.ts +2 -0
- package/src/components/Device/DeviceBackups/DeviceBackups.tsx +11 -36
- package/src/components/Plan/BackupEvents/BackupEvents.module.scss +65 -0
- package/src/components/Plan/BackupEvents/BackupEvents.tsx +65 -4
- package/src/components/Plan/BackupProgress/BackupProgress.module.scss +121 -3
- package/src/components/Plan/BackupProgress/BackupProgress.tsx +149 -71
- package/src/components/Plan/Backups/Backups.tsx +52 -4
- package/src/components/Plan/EditPlan/EditPlan.tsx +1 -0
- package/src/components/Plan/Mirrors/MirrorDetails.module.scss +76 -0
- package/src/components/Plan/Mirrors/MirrorDetails.tsx +100 -0
- package/src/components/Plan/Mirrors/MirrorStatusBadge.module.scss +25 -0
- package/src/components/Plan/Mirrors/MirrorStatusBadge.tsx +65 -0
- package/src/components/Plan/Mirrors/MirrorStorageSelector.module.scss +97 -0
- package/src/components/Plan/Mirrors/MirrorStorageSelector.tsx +70 -0
- package/src/components/Plan/Mirrors/MirrorStorageSelectorModal.tsx +40 -0
- package/src/components/Plan/PlanBackups/PlanBackups.tsx +4 -1
- package/src/components/Plan/PlanForm/PlanForm.tsx +30 -3
- package/src/components/Plan/PlanItems/PlanItem.tsx +3 -3
- package/src/components/Plan/PlanRemoveModal/PlanRemoveModal.tsx +1 -1
- package/src/components/Plan/PlanSettings/PlanReplicationSettings.module.scss +105 -0
- package/src/components/Plan/PlanSettings/PlanReplicationSettings.tsx +334 -0
- package/src/components/Plan/PlanStats/PlanStats.module.scss +1 -1
- package/src/components/Plan/PlanStats/PlanStats.tsx +8 -8
- package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.module.scss +43 -0
- package/src/components/Plan/PlanStorageInfo/PlanStorageInfo.tsx +83 -0
- package/src/components/Restore/RestoreWizard/RestoreConfirmStep.tsx +2 -0
- package/src/components/Restore/RestoreWizard/RestorePreviewStep.tsx +2 -0
- package/src/components/Restore/RestoreWizard/RestoreSettingsStep.tsx +36 -13
- package/src/components/Restore/RestoreWizard/RestoreWizard.module.scss +4 -0
- package/src/components/Restore/RestoreWizard/RestoreWizard.tsx +9 -1
- package/src/components/Settings/GeneralSettings/GeneralSettings.tsx +1 -1
- package/src/components/Settings/IntegrationSettings/IntegrationSettings.tsx +9 -2
- package/src/components/common/Icon/Icon.tsx +10 -1
- package/src/components/common/PageHeader/PageHeader.module.scss +3 -0
- package/src/components/index.ts +6 -0
- package/src/hooks/usePwaAutoUpdate.ts +51 -11
- package/src/router.tsx +26 -17
- package/src/routes/DeviceSingle/DeviceSingle.tsx +3 -3
- package/src/services/backups.ts +32 -9
- package/src/services/plans.ts +45 -0
- package/src/services/restores.ts +10 -2
- package/src/services/users.ts +14 -5
- package/src/utils/progressHelpers.ts +85 -1
|
@@ -1,41 +1,15 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
1
|
import { NavLink } from 'react-router';
|
|
3
|
-
import { DevicePlan } from '../../../@types/devices';
|
|
2
|
+
import { DevicePlan, DeviceStorage } from '../../../@types/devices';
|
|
4
3
|
import Icon from '../../common/Icon/Icon';
|
|
5
4
|
import classes from './DeviceBackups.module.scss';
|
|
6
5
|
import { formatBytes } from '../../../utils/helpers';
|
|
7
6
|
|
|
8
7
|
interface DeviceBackupsProps {
|
|
9
8
|
plans: DevicePlan[];
|
|
9
|
+
storages: DeviceStorage[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const DeviceBackups = ({ plans }: DeviceBackupsProps) => {
|
|
13
|
-
const plansStorages = useMemo(() => {
|
|
14
|
-
const storages: (DevicePlan['storage'] & { size: number })[] = [];
|
|
15
|
-
|
|
16
|
-
//first calculate each storage's size from the plan size.
|
|
17
|
-
const storageSizes: { [key: string]: number } = {};
|
|
18
|
-
plans.forEach((plan) => {
|
|
19
|
-
const planStorageId = plan.storage.id;
|
|
20
|
-
if (!storageSizes[planStorageId]) {
|
|
21
|
-
storageSizes[planStorageId] = 0;
|
|
22
|
-
}
|
|
23
|
-
storageSizes[planStorageId] += plan.size;
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
plans.forEach((plan) => {
|
|
27
|
-
if (!storages.some((storage) => storage.id === plan.storage.id)) {
|
|
28
|
-
// if (plan.storage.id !== 'local') {
|
|
29
|
-
// TODO: implement this check
|
|
30
|
-
storages.push({ ...plan.storage, size: storageSizes[plan.storage.id] });
|
|
31
|
-
// }
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
return storages;
|
|
35
|
-
}, [plans]);
|
|
36
|
-
|
|
37
|
-
console.log('plansStorages :', plansStorages);
|
|
38
|
-
|
|
12
|
+
const DeviceBackups = ({ plans, storages }: DeviceBackupsProps) => {
|
|
39
13
|
return (
|
|
40
14
|
<div className={classes.devicePlans}>
|
|
41
15
|
{/* Backup Plans */}
|
|
@@ -46,7 +20,7 @@ const DeviceBackups = ({ plans }: DeviceBackupsProps) => {
|
|
|
46
20
|
<div className={classes.widgetContent}>
|
|
47
21
|
{plans.length === 0 && <div className={classes.noData}>No backup plans found for this device.</div>}
|
|
48
22
|
{plans.length > 0 &&
|
|
49
|
-
plans.map(({ id, title, sourceConfig, storage, isActive, method, size }) => (
|
|
23
|
+
plans.map(({ id, title, sourceConfig, storage, isActive, method, size, replicatedStorages }) => (
|
|
50
24
|
<div key={id} className={classes.planItem}>
|
|
51
25
|
<div className={`${classes.status} ${!isActive ? classes.paused : ''}`}>
|
|
52
26
|
<div className={classes.iconBlock}>
|
|
@@ -63,7 +37,7 @@ const DeviceBackups = ({ plans }: DeviceBackupsProps) => {
|
|
|
63
37
|
{sourceConfig.includes.length > 1 ? 'paths' : 'path'}
|
|
64
38
|
</div>
|
|
65
39
|
<div>
|
|
66
|
-
<Icon type="storages" size={12} /> {storage.name}
|
|
40
|
+
<Icon type="storages" size={12} /> {replicatedStorages > 0 ? `${replicatedStorages + 1} storages` : storage.name}
|
|
67
41
|
</div>
|
|
68
42
|
<div>
|
|
69
43
|
<Icon type="disk" size={12} /> {formatBytes(size)}
|
|
@@ -109,12 +83,13 @@ const DeviceBackups = ({ plans }: DeviceBackupsProps) => {
|
|
|
109
83
|
<Icon type="storages" size={12} /> Connected Storages
|
|
110
84
|
</div>
|
|
111
85
|
<div className={classes.widgetContent}>
|
|
112
|
-
{
|
|
113
|
-
{
|
|
114
|
-
|
|
86
|
+
{storages && storages.length === 0 && <div className={classes.noData}>No Remote storages are being used by this device.</div>}
|
|
87
|
+
{storages &&
|
|
88
|
+
storages.length > 0 &&
|
|
89
|
+
storages.map(({ id, name, type, storageTypeName }) => (
|
|
115
90
|
<div key={id} className={classes.planItem}>
|
|
116
91
|
<div className={`${classes.status}`}>
|
|
117
|
-
<div className={classes.iconBlock}
|
|
92
|
+
<div className={classes.iconBlock}>
|
|
118
93
|
{type === 'local' ? <Icon type="storages" size={12} /> : <img src={`/providers/${type}.png`} />}
|
|
119
94
|
</div>
|
|
120
95
|
</div>
|
|
@@ -122,7 +97,7 @@ const DeviceBackups = ({ plans }: DeviceBackupsProps) => {
|
|
|
122
97
|
<h4>
|
|
123
98
|
<NavLink to={`/storage/${id}`}>{name}</NavLink>
|
|
124
99
|
</h4>
|
|
125
|
-
<div className={classes.planStats}>{
|
|
100
|
+
<div className={classes.planStats}>{storageTypeName}</div>
|
|
126
101
|
</div>
|
|
127
102
|
</div>
|
|
128
103
|
))}
|
|
@@ -195,3 +195,68 @@
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
|
|
199
|
+
.mirrorsEventsSection {
|
|
200
|
+
margin-top: 20px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.mirrorEventsGroup {
|
|
204
|
+
margin-bottom: 16px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.mirrorEventsHeader {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: 8px;
|
|
211
|
+
padding: 8px 0;
|
|
212
|
+
font-weight: 600;
|
|
213
|
+
font-size: 0.95em;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.mirrorProviderIcon {
|
|
217
|
+
width: 20px;
|
|
218
|
+
height: 20px;
|
|
219
|
+
object-fit: contain;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.mirrorEventsName {
|
|
223
|
+
color: var(--content-text-color);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.mirrorEventsStatus {
|
|
227
|
+
padding: 2px 10px;
|
|
228
|
+
font-size: 0.85em;
|
|
229
|
+
border-radius: 12px;
|
|
230
|
+
text-transform: capitalize;
|
|
231
|
+
font-weight: 500;
|
|
232
|
+
|
|
233
|
+
&.mirror_pending {
|
|
234
|
+
background-color: var(--primary-color-lighter);
|
|
235
|
+
color: var(--content-text-color-light);
|
|
236
|
+
}
|
|
237
|
+
&.mirror_running {
|
|
238
|
+
background-color: var(--primary-color-lighter);
|
|
239
|
+
color: var(--primary-color);
|
|
240
|
+
}
|
|
241
|
+
&.mirror_completed {
|
|
242
|
+
background-color: var(--success-bg-color);
|
|
243
|
+
color: var(--success-text-color);
|
|
244
|
+
}
|
|
245
|
+
&.mirror_failed {
|
|
246
|
+
background-color: var(--error-bg-color);
|
|
247
|
+
color: var(--error-button-color);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.noEvents {
|
|
252
|
+
padding: 12px;
|
|
253
|
+
color: var(--content-text-color-light);
|
|
254
|
+
font-style: italic;
|
|
255
|
+
font-size: 0.9em;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.errorDetails {
|
|
259
|
+
padding: 10px;
|
|
260
|
+
word-break: break-word;
|
|
261
|
+
color: var(--error-button-color);
|
|
262
|
+
}
|
|
@@ -2,7 +2,7 @@ import Icon from '../../common/Icon/Icon';
|
|
|
2
2
|
import SidePanel from '../../common/SidePanel/SidePanel';
|
|
3
3
|
import { getBackupEventActionMessage, getRestoreEventActionMessage } from '../../../utils/progressHelpers';
|
|
4
4
|
import classes from './BackupEvents.module.scss';
|
|
5
|
-
import { BackupProgressData } from '../../../@types/backups';
|
|
5
|
+
import { BackupProgressData, ReplicationProgressData } from '../../../@types/backups';
|
|
6
6
|
import { useGetBackupProgressOnce } from '../../../services/backups';
|
|
7
7
|
import { useGetRestoreProgressOnce } from '../../../services/restores';
|
|
8
8
|
import { useMemo, useState } from 'react';
|
|
@@ -21,7 +21,7 @@ interface BackupEventsProps {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const BackupEvents = ({ id, type = 'backup', sourceId, sourceType, planId, inProgress, progressData, close }: BackupEventsProps) => {
|
|
24
|
-
const [showError, setShowError] = useState<false |
|
|
24
|
+
const [showError, setShowError] = useState<false | string>(false);
|
|
25
25
|
const { data: fetchedProgressData, isLoading } =
|
|
26
26
|
type === 'backup'
|
|
27
27
|
? useGetBackupProgressOnce({ id, sourceId, sourceType, planId })
|
|
@@ -95,7 +95,7 @@ const BackupEvents = ({ id, type = 'backup', sourceId, sourceType, planId, inPro
|
|
|
95
95
|
<span className={classes.action}>
|
|
96
96
|
{type === 'backup' ? getBackupEventActionMessage(event.action) : getRestoreEventActionMessage(event.action)}
|
|
97
97
|
{event.error && (
|
|
98
|
-
<span className={classes.viewError} onClick={() => setShowError(index)}>
|
|
98
|
+
<span className={classes.viewError} onClick={() => setShowError(`main-${index}`)}>
|
|
99
99
|
View Error
|
|
100
100
|
</span>
|
|
101
101
|
)}
|
|
@@ -105,10 +105,71 @@ const BackupEvents = ({ id, type = 'backup', sourceId, sourceType, planId, inPro
|
|
|
105
105
|
})}
|
|
106
106
|
</ul>
|
|
107
107
|
) : null}
|
|
108
|
+
{/* Mirror Events */}
|
|
109
|
+
{progressDataToUse?.mirrors && Object.keys(progressDataToUse.mirrors).length > 0 && (
|
|
110
|
+
<div className={classes.mirrorsEventsSection}>
|
|
111
|
+
{Object.entries(progressDataToUse.mirrors as Record<string, ReplicationProgressData>).map(([storageId, mirror]) => (
|
|
112
|
+
<div key={storageId} className={classes.mirrorEventsGroup}>
|
|
113
|
+
<div className={classes.mirrorEventsHeader}>
|
|
114
|
+
<img src={`/providers/${mirror.storageType}.png`} className={classes.mirrorProviderIcon} />
|
|
115
|
+
<span className={classes.mirrorEventsName}>{mirror.storageName}</span>
|
|
116
|
+
<span className={`${classes.mirrorEventsStatus} ${classes[`mirror_${mirror.status}`] || ''}`}>{mirror.status}</span>
|
|
117
|
+
</div>
|
|
118
|
+
{mirror.events && mirror.events.length > 0 ? (
|
|
119
|
+
<ul className={classes.eventList}>
|
|
120
|
+
{mirror.events.map((event: any, index: number) => {
|
|
121
|
+
const isError = event.error;
|
|
122
|
+
const isCompleted = event.action === 'REPLICATION_COMPLETE';
|
|
123
|
+
const isFailed = event.action === 'REPLICATION_FAILED';
|
|
124
|
+
return (
|
|
125
|
+
<li
|
|
126
|
+
key={index}
|
|
127
|
+
className={`${classes.eventItem} ${isError ? classes.error : ''} ${isCompleted ? classes.completed : ''} ${isFailed ? classes.failed : ''}`}
|
|
128
|
+
>
|
|
129
|
+
<span className={classes.icon}>
|
|
130
|
+
<Icon type={isError ? 'error-circle-filled' : 'check-circle-filled'} size={16} />
|
|
131
|
+
</span>
|
|
132
|
+
<span className={classes.time}>{new Date(event.timestamp).toLocaleTimeString()}</span>
|
|
133
|
+
<span className={classes.phase}>{event.phase}</span>
|
|
134
|
+
<span className={classes.action}>
|
|
135
|
+
{getBackupEventActionMessage(event.action, mirror.storageName)}
|
|
136
|
+
{event.error && (
|
|
137
|
+
<span className={classes.viewError} onClick={() => setShowError(`mirror-${storageId}-${index}`)}>
|
|
138
|
+
View Error
|
|
139
|
+
</span>
|
|
140
|
+
)}
|
|
141
|
+
</span>
|
|
142
|
+
</li>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
145
|
+
</ul>
|
|
146
|
+
) : (
|
|
147
|
+
<div className={classes.noEvents}>No events recorded</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
108
153
|
</div>
|
|
109
154
|
{showError && (
|
|
110
155
|
<Modal title="Error Details" closeModal={() => setShowError(false)} width="400px">
|
|
111
|
-
<div className={classes.errorDetails}>
|
|
156
|
+
<div className={classes.errorDetails}>
|
|
157
|
+
{(() => {
|
|
158
|
+
if (typeof showError === 'string') {
|
|
159
|
+
if (showError.startsWith('main-')) {
|
|
160
|
+
const idx = parseInt(showError.replace('main-', ''));
|
|
161
|
+
return progressDataToUse?.events[idx]?.error || 'Unknown error occurred.';
|
|
162
|
+
}
|
|
163
|
+
if (showError.startsWith('mirror-')) {
|
|
164
|
+
const parts = showError.replace('mirror-', '').split('-');
|
|
165
|
+
const eventIdx = parseInt(parts.pop()!);
|
|
166
|
+
const sId = parts.join('-');
|
|
167
|
+
return (progressDataToUse?.mirrors as any)?.[sId]?.events?.[eventIdx]?.error || 'Unknown error occurred.';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return 'Unknown error occurred.';
|
|
171
|
+
})()}
|
|
172
|
+
</div>
|
|
112
173
|
</Modal>
|
|
113
174
|
)}
|
|
114
175
|
</SidePanel>
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
.backup {
|
|
11
11
|
width: 100%;
|
|
12
12
|
padding: 10px 15px;
|
|
13
|
-
display: flex;
|
|
14
|
-
justify-content: left;
|
|
15
|
-
align-items: center;
|
|
16
13
|
box-sizing: border-box;
|
|
17
14
|
border-radius: 6px;
|
|
18
15
|
border: 1px solid var(--line-color);
|
|
19
16
|
margin-bottom: 15px;
|
|
17
|
+
.backupProgress {
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: left;
|
|
20
|
+
align-items: center;
|
|
21
|
+
}
|
|
20
22
|
.backupIcon {
|
|
21
23
|
margin-right: 15px;
|
|
22
24
|
}
|
|
@@ -118,3 +120,119 @@
|
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
122
|
}
|
|
123
|
+
|
|
124
|
+
.mirrorsSection {
|
|
125
|
+
width: 100%;
|
|
126
|
+
margin-top: 10px;
|
|
127
|
+
padding: 10px;
|
|
128
|
+
box-sizing: border-box;
|
|
129
|
+
border-top: 1px solid var(--line-color);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.mirrorsTitle {
|
|
133
|
+
font-size: 0.6875rem;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
color: var(--content-text-color-light);
|
|
136
|
+
margin-bottom: 8px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.mirrorsList {
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-direction: column;
|
|
142
|
+
gap: 6px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.mirrorItem {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: row;
|
|
148
|
+
gap: 10px;
|
|
149
|
+
}
|
|
150
|
+
.mirrorProgress {
|
|
151
|
+
width: 100%;
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 5px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.mirrorStorageIcon {
|
|
158
|
+
padding: 5px;
|
|
159
|
+
border-radius: 4px;
|
|
160
|
+
border: 1px solid var(--line-color);
|
|
161
|
+
position: relative;
|
|
162
|
+
span {
|
|
163
|
+
position: absolute;
|
|
164
|
+
z-index: 2;
|
|
165
|
+
top: -5px;
|
|
166
|
+
margin-left: -10px;
|
|
167
|
+
}
|
|
168
|
+
img {
|
|
169
|
+
max-width: 20px;
|
|
170
|
+
vertical-align: middle;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.mirrorInfo {
|
|
175
|
+
display: flex;
|
|
176
|
+
justify-content: space-between;
|
|
177
|
+
align-items: center;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mirrorName {
|
|
181
|
+
font-size: 0.7rem;
|
|
182
|
+
color: var(--content-text-color);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.mirrorStatus {
|
|
186
|
+
font-size: 0.7rem;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
color: var(--content-text-color-light);
|
|
189
|
+
&.mirrorCompleted {
|
|
190
|
+
color: var(--success-text-color);
|
|
191
|
+
}
|
|
192
|
+
&.mirrorFailed {
|
|
193
|
+
color: var(--error-button-color);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.mirrorBar {
|
|
198
|
+
width: 100%;
|
|
199
|
+
height: 6px;
|
|
200
|
+
border-radius: 3px;
|
|
201
|
+
background-color: var(--line-color);
|
|
202
|
+
overflow: hidden;
|
|
203
|
+
.mirrorBarFill {
|
|
204
|
+
height: 100%;
|
|
205
|
+
border-radius: 3px;
|
|
206
|
+
transition: all 0.3s linear;
|
|
207
|
+
&.mirrorCompleted {
|
|
208
|
+
background-color: turquoise;
|
|
209
|
+
}
|
|
210
|
+
&.mirrorFailed {
|
|
211
|
+
background-color: var(--error-button-color);
|
|
212
|
+
}
|
|
213
|
+
&.mirrorRunning {
|
|
214
|
+
background-color: turquoise;
|
|
215
|
+
}
|
|
216
|
+
&.mirrorPending {
|
|
217
|
+
background-color: var(--line-color);
|
|
218
|
+
width: 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&.mirrorRunningIndeterminate {
|
|
222
|
+
background-color: turquoise;
|
|
223
|
+
width: 100% !important;
|
|
224
|
+
opacity: 0.4;
|
|
225
|
+
animation: mirrorPulse 1.8s ease-in-out infinite;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@keyframes mirrorPulse {
|
|
231
|
+
0%,
|
|
232
|
+
100% {
|
|
233
|
+
opacity: 0.25;
|
|
234
|
+
}
|
|
235
|
+
50% {
|
|
236
|
+
opacity: 0.6;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -5,12 +5,18 @@ import Icon from '../../common/Icon/Icon';
|
|
|
5
5
|
import { useCancelBackup, useGetBackupProgress } from '../../../services/backups';
|
|
6
6
|
import classes from './BackupProgress.module.scss';
|
|
7
7
|
import { formatBytes, formatDateTime, formatSeconds, timeAgo } from '../../../utils/helpers';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
generateBackupProgressMessage,
|
|
10
|
+
extractResticData,
|
|
11
|
+
generateRestoreProgressMessage,
|
|
12
|
+
generateMirrorProgressMessage,
|
|
13
|
+
} from '../../../utils/progressHelpers';
|
|
9
14
|
import { Backup } from '../../../@types/backups';
|
|
10
15
|
import { RestoreSlim } from '../../../@types/restores';
|
|
11
16
|
import ActionModal from '../../common/ActionModal/ActionModal';
|
|
12
17
|
import { useCancelRestore, useGetRestoreProgress } from '../../../services/restores';
|
|
13
18
|
import BackupEvents from '../BackupEvents/BackupEvents';
|
|
19
|
+
import { ReplicationProgressData } from '../../../@types/backups';
|
|
14
20
|
|
|
15
21
|
interface BackupProgressProps {
|
|
16
22
|
item: Backup | RestoreSlim;
|
|
@@ -127,84 +133,156 @@ const BackupProgress = ({ item, sourceId, sourceType, planId, type = 'backup' }:
|
|
|
127
133
|
|
|
128
134
|
return (
|
|
129
135
|
<div key={item.id} className={classes.backup}>
|
|
130
|
-
<div className={classes.
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
<div className={classes.backupLeft}>
|
|
134
|
-
<div className={classes.backupId}>
|
|
135
|
-
{type === 'restore' ? 'Restoring ' : ''}backup-{id}
|
|
136
|
-
<span className={classes.backupTime} title={formatDateTime(started)}>
|
|
137
|
-
<Icon type="clock" size={12} /> Started {started ? timeAgo(new Date(started)) : 'a few seconds ago'}
|
|
138
|
-
</span>
|
|
139
|
-
{item.errorMsg && (
|
|
140
|
-
<span className={classes.backupError}>
|
|
141
|
-
<Icon type="error-circle-filled" size={13} />{' '}
|
|
142
|
-
<i data-tooltip-id="htmlToolTip" data-tooltip-place="top" data-tooltip-html={item.errorMsg}>
|
|
143
|
-
<u>Error</u> Occurred.
|
|
144
|
-
</i>{' '}
|
|
145
|
-
Retrying...
|
|
146
|
-
</span>
|
|
147
|
-
)}
|
|
136
|
+
<div key={item.id} className={classes.backupProgress}>
|
|
137
|
+
<div className={classes.backupIcon}>
|
|
138
|
+
<Icon type="loading" size={24} />
|
|
148
139
|
</div>
|
|
149
|
-
<div className={classes.
|
|
150
|
-
<div>
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
<div className={classes.backupLeft}>
|
|
141
|
+
<div className={classes.backupId}>
|
|
142
|
+
{type === 'restore' ? 'Restoring ' : ''}backup-{id}
|
|
143
|
+
<span className={classes.backupTime} title={formatDateTime(started)}>
|
|
144
|
+
<Icon type="clock" size={12} /> Started {started ? timeAgo(new Date(started)) : 'a few seconds ago'}
|
|
153
145
|
</span>
|
|
154
|
-
{
|
|
146
|
+
{item.errorMsg && (
|
|
147
|
+
<span className={classes.backupError}>
|
|
148
|
+
<Icon type="error-circle-filled" size={13} />{' '}
|
|
149
|
+
<i data-tooltip-id="htmlToolTip" data-tooltip-place="top" data-tooltip-html={item.errorMsg}>
|
|
150
|
+
<u>Error</u> Occurred.
|
|
151
|
+
</i>{' '}
|
|
152
|
+
Retrying...
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
155
|
</div>
|
|
156
|
-
<
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</span>
|
|
167
|
-
<i></i>
|
|
168
|
-
<span>
|
|
169
|
-
{formatBytes(bytesProcessed)} / {formatBytes(totalBytes)}
|
|
170
|
-
</span>
|
|
156
|
+
<div className={classes.backupStart}>
|
|
157
|
+
<div>
|
|
158
|
+
<span className={classes.progressMessage} onClick={() => setShowProgressDetails(true)}>
|
|
159
|
+
{progressMessage}
|
|
160
|
+
</span>
|
|
161
|
+
{/* <Icon type="clock" size={12} /> Started {started ? timeAgo(new Date(started)) : 'a few seconds ago'} */}
|
|
162
|
+
</div>
|
|
163
|
+
<button onClick={() => setShowCancelModal(true)} title={`Cancel ${type}`}>
|
|
164
|
+
<Icon type="close" size={12} /> Cancel
|
|
165
|
+
</button>
|
|
171
166
|
</div>
|
|
172
|
-
<div className={classes.ProgressStatsRight}>{type !== 'restore' && <>Remaining: {formatSeconds(seconds_remaining)}</>}</div>
|
|
173
167
|
</div>
|
|
174
|
-
<div className={classes.
|
|
175
|
-
<div
|
|
176
|
-
className={
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
<div className={classes.backupRight}>
|
|
169
|
+
<div className={classes.ProgressStats}>
|
|
170
|
+
<div className={classes.ProgressStatsLeft}>
|
|
171
|
+
<span>
|
|
172
|
+
{filesProcessed} / {totalFiles} Files
|
|
173
|
+
</span>
|
|
174
|
+
<i></i>
|
|
175
|
+
<span>
|
|
176
|
+
{formatBytes(bytesProcessed)} / {formatBytes(totalBytes)}
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div className={classes.ProgressStatsRight}>{type !== 'restore' && <>Remaining: {formatSeconds(seconds_remaining)}</>}</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div className={classes.progressBar}>
|
|
182
|
+
<div
|
|
183
|
+
className={`${classes.progressBarFill} ${progressPercent > 3 ? classes.progressBarFilled : ''}`}
|
|
184
|
+
style={{ width: progressPercent + '%' }}
|
|
185
|
+
>
|
|
186
|
+
<span>{progressPercent}%</span>
|
|
187
|
+
</div>
|
|
180
188
|
</div>
|
|
181
189
|
</div>
|
|
190
|
+
|
|
191
|
+
{showCancelModal && (
|
|
192
|
+
<ActionModal
|
|
193
|
+
title={`Cancel ${type}`}
|
|
194
|
+
message={<>{`Are you sure you want to cancel the ${type} process?`}</>}
|
|
195
|
+
closeModal={() => setShowCancelModal(false)}
|
|
196
|
+
width="400px"
|
|
197
|
+
primaryAction={{
|
|
198
|
+
title: `Yes, Cancel ${type}`,
|
|
199
|
+
type: 'danger',
|
|
200
|
+
isPending: type === 'restore' ? cancelRestoreMutation.isPending : cancelBackupMutation.isPending,
|
|
201
|
+
action: () => cancel(),
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
{showProgressDetails && (
|
|
206
|
+
<BackupEvents
|
|
207
|
+
id={id}
|
|
208
|
+
type={type}
|
|
209
|
+
planId={planId}
|
|
210
|
+
sourceId={sourceId}
|
|
211
|
+
sourceType={sourceType}
|
|
212
|
+
progressData={progressData}
|
|
213
|
+
inProgress={true}
|
|
214
|
+
close={() => setShowProgressDetails(false)}
|
|
215
|
+
/>
|
|
216
|
+
)}
|
|
182
217
|
</div>
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
218
|
+
{/* Mirrors Progress */}
|
|
219
|
+
{progressData?.mirrors && Object.keys(progressData.mirrors).length > 0 && (
|
|
220
|
+
<div className={classes.mirrorsSection}>
|
|
221
|
+
<div className={classes.mirrorsTitle}>Replicating</div>
|
|
222
|
+
<div className={classes.mirrorsList}>
|
|
223
|
+
{Object.entries(progressData.mirrors as Record<string, ReplicationProgressData>).map(([storageId, mirror]) => {
|
|
224
|
+
const mirrorResticData = mirror.events
|
|
225
|
+
?.slice()
|
|
226
|
+
.reverse()
|
|
227
|
+
.find((e: any) => e.resticData)?.resticData;
|
|
228
|
+
const mirrorPercent = mirrorResticData?.percent_done ? Math.round(mirrorResticData.percent_done * 100) : 0;
|
|
229
|
+
const mirrorMessage = generateMirrorProgressMessage(mirror);
|
|
230
|
+
|
|
231
|
+
let mirrorStatusClass = classes.mirrorPending;
|
|
232
|
+
if (mirror.status === 'running')
|
|
233
|
+
mirrorStatusClass = mirrorPercent > 0 ? classes.mirrorRunning : classes.mirrorRunningIndeterminate;
|
|
234
|
+
if (mirror.status === 'completed') mirrorStatusClass = classes.mirrorCompleted;
|
|
235
|
+
if (mirror.status === 'failed') mirrorStatusClass = classes.mirrorFailed;
|
|
236
|
+
if (mirror.status === 'retrying') mirrorStatusClass = classes.mirrorRunningIndeterminate;
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div key={storageId} className={classes.mirrorItem}>
|
|
240
|
+
<div className={classes.mirrorStorageIcon}>
|
|
241
|
+
{(mirror.status === 'running' || mirror.status === 'pending' || mirror.status === 'retrying') && (
|
|
242
|
+
<Icon type="loading" size={12} />
|
|
243
|
+
)}
|
|
244
|
+
<img src={`/providers/${mirror.storageType}.png`} />
|
|
245
|
+
</div>
|
|
246
|
+
<div className={classes.mirrorProgress}>
|
|
247
|
+
<div className={classes.mirrorInfo}>
|
|
248
|
+
<span className={classes.mirrorName}>{mirror.storageName}</span>
|
|
249
|
+
<span className={`${classes.mirrorStatus} ${mirrorStatusClass}`}>
|
|
250
|
+
{mirror.status === 'pending' && 'Pending'}
|
|
251
|
+
{mirror.status === 'running' && (mirrorPercent > 0 ? `${mirrorPercent}% — ${mirrorMessage}` : mirrorMessage)}
|
|
252
|
+
{mirror.status === 'completed' && (
|
|
253
|
+
<>
|
|
254
|
+
<Icon type="check" size={10} /> Completed
|
|
255
|
+
</>
|
|
256
|
+
)}
|
|
257
|
+
{mirror.status === 'failed' && (
|
|
258
|
+
<span data-tooltip-id="htmlToolTip" data-tooltip-html={mirror.error || 'Replication failed'}>
|
|
259
|
+
<Icon type="error" size={12} /> Failed
|
|
260
|
+
</span>
|
|
261
|
+
)}
|
|
262
|
+
{mirror.status === 'retrying' && mirrorMessage}
|
|
263
|
+
</span>
|
|
264
|
+
</div>
|
|
265
|
+
<div className={classes.mirrorBar}>
|
|
266
|
+
<div
|
|
267
|
+
className={`${classes.mirrorBarFill} ${mirrorStatusClass}`}
|
|
268
|
+
style={{
|
|
269
|
+
width:
|
|
270
|
+
mirror.status === 'completed'
|
|
271
|
+
? '100%'
|
|
272
|
+
: mirror.status === 'failed'
|
|
273
|
+
? '100%'
|
|
274
|
+
: mirrorPercent > 0
|
|
275
|
+
? `${mirrorPercent}%`
|
|
276
|
+
: undefined,
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
})}
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
208
286
|
)}
|
|
209
287
|
</div>
|
|
210
288
|
);
|