@plutonhq/core-frontend 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +121 -94
- package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +16 -0
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +115 -0
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -0
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +26 -0
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -0
- 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/PlanPendingBackup/PlanPendingBackup.d.ts +2 -2
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanPendingBackup/PlanPendingBackup.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.d.ts +1 -1
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.d.ts.map +1 -1
- package/dist-lib/components/Settings/GeneralSettings/GeneralSettings.js +52 -24
- 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/Settings/TwoFactorSetup/TwoFactorSetup.d.ts +7 -0
- package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.d.ts.map +1 -0
- package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.js +79 -0
- package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.js.map +1 -0
- package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss.js +24 -0
- package/dist-lib/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss.js.map +1 -0
- 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 +6 -0
- package/dist-lib/components/index.d.ts.map +1 -1
- package/dist-lib/components.js +102 -90
- 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/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.js +123 -98
- package/dist-lib/routes/PlanSingle/PlanSingle.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 +20 -0
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +227 -172
- 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/settings.d.ts +16 -0
- package/dist-lib/services/settings.d.ts.map +1 -1
- package/dist-lib/services/settings.js +147 -68
- package/dist-lib/services/settings.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 +113 -101
- 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 +44 -17
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +110 -0
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +187 -0
- package/src/components/Plan/PlanItems/PlanItem.tsx +3 -3
- package/src/components/Plan/PlanPendingBackup/PlanPendingBackup.tsx +2 -2
- 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 +38 -2
- package/src/components/Settings/IntegrationSettings/IntegrationSettings.tsx +9 -2
- package/src/components/Settings/TwoFactorSetup/TwoFactorSetup.module.scss +62 -0
- package/src/components/Settings/TwoFactorSetup/TwoFactorSetup.tsx +102 -0
- package/src/components/common/Icon/Icon.tsx +10 -1
- package/src/components/common/PageHeader/PageHeader.module.scss +3 -0
- package/src/components/index.ts +8 -0
- package/src/hooks/usePwaAutoUpdate.ts +51 -11
- package/src/router.tsx +26 -17
- package/src/routes/DeviceSingle/DeviceSingle.tsx +3 -3
- package/src/routes/PlanSingle/PlanSingle.tsx +21 -0
- package/src/services/backups.ts +32 -9
- package/src/services/plans.ts +75 -0
- package/src/services/restores.ts +10 -2
- package/src/services/settings.ts +90 -0
- package/src/services/users.ts +14 -5
- package/src/utils/progressHelpers.ts +85 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ActionModal } from '../..';
|
|
1
3
|
import { useTheme } from '../../../context/ThemeContext';
|
|
2
4
|
import Input from '../../common/form/Input/Input';
|
|
5
|
+
import Toggle from '../../common/form/Toggle/Toggle';
|
|
3
6
|
import Tristate from '../../common/form/Tristate/Tristate';
|
|
7
|
+
import TwoFactorSetup from '../TwoFactorSetup/TwoFactorSetup';
|
|
4
8
|
import classes from './GeneralSettings.module.scss';
|
|
5
9
|
|
|
6
10
|
interface GeneralSettingsProps {
|
|
@@ -9,8 +13,22 @@ interface GeneralSettingsProps {
|
|
|
9
13
|
onUpdate: (settings: Record<string, any>) => void;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
const GeneralSettings = ({ settings, onUpdate }: GeneralSettingsProps) => {
|
|
16
|
+
const GeneralSettings = ({ settings, settingsID, onUpdate }: GeneralSettingsProps) => {
|
|
13
17
|
const { setTheme } = useTheme();
|
|
18
|
+
const [show2FASetupConfirm, setShow2FASetupConfirm] = useState(false);
|
|
19
|
+
const [show2FASetup, setShow2FASetup] = useState(false);
|
|
20
|
+
|
|
21
|
+
const { totp } = settings || {};
|
|
22
|
+
const is2FASetupComplete = totp?.secret;
|
|
23
|
+
|
|
24
|
+
const update2FASetting = (enabled: boolean) => {
|
|
25
|
+
if (enabled === true && !is2FASetupComplete) {
|
|
26
|
+
setShow2FASetupConfirm(true);
|
|
27
|
+
return;
|
|
28
|
+
} else {
|
|
29
|
+
onUpdate({ ...settings, totp: { ...totp, enabled } });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
14
32
|
|
|
15
33
|
const handleThemeChange = (newThemeValue: 'auto' | 'light' | 'dark') => {
|
|
16
34
|
setTheme(newThemeValue);
|
|
@@ -55,9 +73,27 @@ const GeneralSettings = ({ settings, onUpdate }: GeneralSettingsProps) => {
|
|
|
55
73
|
{ label: 'Light', value: 'light' },
|
|
56
74
|
]}
|
|
57
75
|
onUpdate={(val: string) => handleThemeChange(val as 'auto' | 'light' | 'dark')}
|
|
58
|
-
inline={
|
|
76
|
+
inline={false}
|
|
59
77
|
/>
|
|
60
78
|
</div>
|
|
79
|
+
<div className={classes.field}>
|
|
80
|
+
<Toggle label="Enable 2FA" fieldValue={settings?.totp?.enabled || false} onUpdate={(val) => update2FASetting(val)} inline={true} />
|
|
81
|
+
</div>
|
|
82
|
+
{show2FASetupConfirm && (
|
|
83
|
+
<ActionModal
|
|
84
|
+
title={`Enable Two-Factor Authentication (2FA)`}
|
|
85
|
+
message={`Are you sure you want to enable Two-Factor Authentication (2FA) to secure Pluton Login? You will be required to use an authenticator app to login if you enable this feature.`}
|
|
86
|
+
closeModal={() => setShow2FASetupConfirm(false)}
|
|
87
|
+
width="420px"
|
|
88
|
+
primaryAction={{
|
|
89
|
+
title: `Yes, Enable 2FA`,
|
|
90
|
+
type: 'default',
|
|
91
|
+
isPending: false,
|
|
92
|
+
action: () => setShow2FASetup(true),
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
{show2FASetup && <TwoFactorSetup id={settingsID} close={() => setShow2FASetup(false)} />}
|
|
61
97
|
</div>
|
|
62
98
|
);
|
|
63
99
|
};
|
|
@@ -19,12 +19,19 @@ const IntegrationSettings = ({ settingsID, settings, onUpdate }: IntegrationSett
|
|
|
19
19
|
|
|
20
20
|
const validationMutation = useValidateIntegration();
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const onIntegrationUpdate = (key: string, intSettings: Record<string, any>) => {
|
|
23
|
+
console.log('onIntegrationUpdate :', key, intSettings);
|
|
24
|
+
onUpdate({ ...integrationSettings, [key]: { ...integrationSettings[key], ...intSettings } });
|
|
25
|
+
};
|
|
23
26
|
|
|
24
27
|
return (
|
|
25
28
|
<div className={classes.integrations}>
|
|
26
29
|
<div>
|
|
27
|
-
<SMTPSettings
|
|
30
|
+
<SMTPSettings
|
|
31
|
+
settings={smtp}
|
|
32
|
+
onUpdate={(iSettings) => onIntegrationUpdate('smtp', iSettings)}
|
|
33
|
+
showTestModal={(type) => setShowEmailTestModal(type)}
|
|
34
|
+
/>
|
|
28
35
|
</div>
|
|
29
36
|
{showEmailTestModal && (
|
|
30
37
|
<ActionModal
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
.setupContainer {
|
|
2
|
+
.errorMsg {
|
|
3
|
+
background-color: var(--error-bg-color);
|
|
4
|
+
color: var(--error-text-color);
|
|
5
|
+
padding: 6px 12px;
|
|
6
|
+
border-radius: 6px;
|
|
7
|
+
}
|
|
8
|
+
.instructions {
|
|
9
|
+
ol {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 12px 20px;
|
|
12
|
+
padding-top: 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
.qrSection {
|
|
16
|
+
.qrCode {
|
|
17
|
+
background-color: var(--background-color);
|
|
18
|
+
padding: 12px;
|
|
19
|
+
text-align: center;
|
|
20
|
+
border-radius: 6px 6px 0 0;
|
|
21
|
+
}
|
|
22
|
+
.setupKey {
|
|
23
|
+
padding: 6px;
|
|
24
|
+
margin-bottom: 12px;
|
|
25
|
+
background: var(--primary-color-light);
|
|
26
|
+
text-align: center;
|
|
27
|
+
border-radius: 0 0 6px 6px;
|
|
28
|
+
}
|
|
29
|
+
.verifySection {
|
|
30
|
+
display: flex;
|
|
31
|
+
& > div:nth-child(1) {
|
|
32
|
+
width: calc(100% - 140px);
|
|
33
|
+
}
|
|
34
|
+
& > button {
|
|
35
|
+
margin-left: 12px;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.successMessage {
|
|
42
|
+
.successMsg {
|
|
43
|
+
background-color: var(--success-bg-color);
|
|
44
|
+
color: var(--success-text-color);
|
|
45
|
+
padding: 6px 12px;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
}
|
|
49
|
+
strong {
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
}
|
|
52
|
+
code {
|
|
53
|
+
background-color: var(--line-color);
|
|
54
|
+
width: 100%;
|
|
55
|
+
display: block;
|
|
56
|
+
padding: 12px;
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
white-space: pre;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useSetupTwoFactorAuth, useVerifyTwoFactorAuth } from '../../../services';
|
|
3
|
+
import Icon from '../../common/Icon/Icon';
|
|
4
|
+
import Modal from '../../common/Modal/Modal';
|
|
5
|
+
import Input from '../../common/form/Input/Input';
|
|
6
|
+
import Button from '../../common/Button/Button';
|
|
7
|
+
import classes from './TwoFactorSetup.module.scss';
|
|
8
|
+
|
|
9
|
+
interface TwoFactorSetupProps {
|
|
10
|
+
id: number;
|
|
11
|
+
close: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TwoFactorSetup = ({ close, id = 1 }: TwoFactorSetupProps) => {
|
|
15
|
+
const [verificationCode, setVerificationCode] = useState('');
|
|
16
|
+
const setupMutation = useSetupTwoFactorAuth();
|
|
17
|
+
const verifyMutation = useVerifyTwoFactorAuth();
|
|
18
|
+
const isLoading = setupMutation.isPending || verifyMutation.isPending;
|
|
19
|
+
|
|
20
|
+
const qrCodeUrl: string = setupMutation.data?.result?.qrCodeDataUrl || '';
|
|
21
|
+
const setupKey: string = setupMutation.data?.result?.setupKey || '';
|
|
22
|
+
const recoveryCodes: string[] = verifyMutation.data?.result?.recoveryCodes || [];
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setupMutation.mutate(id);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const verify2FA = () => {
|
|
29
|
+
verifyMutation.mutate({ code: verificationCode, id });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{!verifyMutation.isSuccess && (
|
|
35
|
+
<Modal title="Two-Factor Authentication (2FA) Setup" closeModal={() => !isLoading && close()} width="500px" disableBackdropClick={true}>
|
|
36
|
+
<div className={classes.twoFactorSetup}>
|
|
37
|
+
{setupMutation.isPending && (
|
|
38
|
+
<p>
|
|
39
|
+
<Icon type="loading" /> Setting up 2FA...
|
|
40
|
+
</p>
|
|
41
|
+
)}
|
|
42
|
+
{setupMutation.isError && <p className={classes.error}>Error setting up 2FA. Please try again.</p>}
|
|
43
|
+
{setupMutation.isSuccess && (
|
|
44
|
+
<div className={classes.setupContainer}>
|
|
45
|
+
<div className={classes.instructions}>
|
|
46
|
+
<h4>Setup Instructions</h4>
|
|
47
|
+
<ol>
|
|
48
|
+
<li>
|
|
49
|
+
Download and install an authenticator app on your mobile device (e.g., Google Authenticator, Authy, Microsoft
|
|
50
|
+
Authenticator).
|
|
51
|
+
</li>
|
|
52
|
+
<li>Open the authenticator app and choose to add a new account.</li>
|
|
53
|
+
<li>Scan the QR code below using the authenticator app or manually enter the setup key provided.</li>
|
|
54
|
+
<li>Once added, the app will generate a 6-digit verification code that refreshes every 30 seconds.</li>
|
|
55
|
+
<li>Enter the current 6-digit code from the authenticator app into the field below to complete the setup.</li>
|
|
56
|
+
</ol>
|
|
57
|
+
</div>
|
|
58
|
+
<div className={classes.qrSection}>
|
|
59
|
+
<div className={classes.qrCode}>{qrCodeUrl && <img src={qrCodeUrl} alt="2FA QR Code" />}</div>
|
|
60
|
+
<div className={classes.setupKey}>Setup Key: {setupKey}</div>
|
|
61
|
+
{verifyMutation.isError && <p className={classes.errorMsg}>{verifyMutation.error?.message}</p>}
|
|
62
|
+
<div className={classes.verifySection}>
|
|
63
|
+
<Input
|
|
64
|
+
type="text"
|
|
65
|
+
placeholder="Enter 6-digit code"
|
|
66
|
+
fieldValue={verificationCode}
|
|
67
|
+
onUpdate={(val) => setVerificationCode(val)}
|
|
68
|
+
full={true}
|
|
69
|
+
/>
|
|
70
|
+
<Button text="Verify & Enable 2FA" onClick={() => verify2FA()} variant="primary" size="sm" />
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</Modal>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{verifyMutation.isSuccess && (
|
|
80
|
+
<Modal
|
|
81
|
+
title="Two-Factor Authentication (2FA) Setup"
|
|
82
|
+
closeModal={() => !isLoading && location.reload()}
|
|
83
|
+
width="500px"
|
|
84
|
+
disableBackdropClick={true}
|
|
85
|
+
>
|
|
86
|
+
<div className={classes.successMessage}>
|
|
87
|
+
<p className={classes.successMsg}>Two-Factor Authentication (2FA) has been successfully enabled.</p>
|
|
88
|
+
<p>
|
|
89
|
+
<strong>
|
|
90
|
+
Save these backup codes in a secure place. They will be required to access your account if you lose your device. They won't be
|
|
91
|
+
shown again.
|
|
92
|
+
</strong>
|
|
93
|
+
</p>
|
|
94
|
+
<code className={classes.recoveryCodes}>{recoveryCodes.join('\n')}</code>
|
|
95
|
+
</div>
|
|
96
|
+
</Modal>
|
|
97
|
+
)}
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default TwoFactorSetup;
|
|
@@ -125,7 +125,16 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
|
|
|
125
125
|
</g>
|
|
126
126
|
</IconWrapper>
|
|
127
127
|
)}
|
|
128
|
-
|
|
128
|
+
{type === 'mirrors' && (
|
|
129
|
+
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
130
|
+
<path
|
|
131
|
+
fill={color}
|
|
132
|
+
d="M21.53 5.15a.99.99 0 0 0-.97-.04l-10 5a1 1 0 0 0-.55.89v10a1 1 0 0 0 1 1c.15 0 .31-.04.45-.11l10-5a1 1 0 0 0 .55-.89V6c0-.35-.18-.67-.47-.85ZM20 15.38l-8 4v-7.76l8-4z"
|
|
133
|
+
/>
|
|
134
|
+
<path fill={color} d="m16.55 3.11l-10 5A1 1 0 0 0 6 9v10h2V9.62l9.45-4.72l-.89-1.79Z" />
|
|
135
|
+
<path fill={color} d="m12.55 1.11l-10 5A1 1 0 0 0 2 7v10h2V7.62l9.45-4.73l-.89-1.79Z" />
|
|
136
|
+
</IconWrapper>
|
|
137
|
+
)}
|
|
129
138
|
{type === 'devices' && (
|
|
130
139
|
<IconWrapper size={size} viewBox="0 0 24 24">
|
|
131
140
|
<path
|
package/src/components/index.ts
CHANGED
|
@@ -88,6 +88,13 @@ export { default as PlanStrategySettings } from './Plan/PlanSettings/PlanStrateg
|
|
|
88
88
|
export { default as PlanTypeSettings } from './Plan/PlanSettings/PlanTypeSettings';
|
|
89
89
|
export { default as PlanStats } from './Plan/PlanStats/PlanStats';
|
|
90
90
|
export { default as PlanUnlockModal } from './Plan/PlanUnlockModal/PlanUnlockModal';
|
|
91
|
+
export { default as PlanIntegrity } from './Plan/PlanIntegrity/PlanIntegrity';
|
|
92
|
+
|
|
93
|
+
// Mirror/Replication components
|
|
94
|
+
export { default as MirrorStatusBadge } from './Plan/Mirrors/MirrorStatusBadge';
|
|
95
|
+
export { default as MirrorStorageSelector } from './Plan/Mirrors/MirrorStorageSelector';
|
|
96
|
+
export { default as MirrorStorageSelectorModal } from './Plan/Mirrors/MirrorStorageSelectorModal';
|
|
97
|
+
export { default as PlanReplicationSettings } from './Plan/PlanSettings/PlanReplicationSettings';
|
|
91
98
|
|
|
92
99
|
// Restore components
|
|
93
100
|
export { default as PlanRestores } from './Plan/Restores/Restores';
|
|
@@ -105,6 +112,7 @@ export { default as AppLogs } from './Settings/AppLogs/AppLogs';
|
|
|
105
112
|
export { default as GeneralSettings } from './Settings/GeneralSettings/GeneralSettings';
|
|
106
113
|
export { default as IntegrationSettings } from './Settings/IntegrationSettings/IntegrationSettings';
|
|
107
114
|
export { default as SMTPSettings } from './Settings/IntegrationSettings/SMTPSettings';
|
|
115
|
+
export { default as TwoFactorSetup } from './Settings/TwoFactorSetup/TwoFactorSetup';
|
|
108
116
|
|
|
109
117
|
// Skeleton components
|
|
110
118
|
export { default as SkeletonItems } from './Skeleton/SkeletonItems';
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { useRegisterSW } from 'virtual:pwa-register/react';
|
|
3
3
|
|
|
4
|
+
const VERSION_STORAGE_KEY = 'pluton_app_version';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Handles two PWA update scenarios:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Workbox precache update** — When a new frontend build is deployed, Workbox detects
|
|
10
|
+
* that the precache manifest changed and sets `needRefresh`. We immediately activate
|
|
11
|
+
* the new service worker so the page reloads with fresh assets.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Backend version mismatch** — Compares the server's `X-App-Version` header
|
|
14
|
+
* (stored on `window.plutonVersion` by `validateAuth`) against the last known version
|
|
15
|
+
* in localStorage. On mismatch (e.g. docker/binary upgrade), unregisters all service
|
|
16
|
+
* workers, clears all caches, and hard-reloads. This catches cases where the SW update
|
|
17
|
+
* check hasn't fired yet (long-lived tabs, cached sw.js, etc.).
|
|
7
18
|
*/
|
|
8
19
|
export function usePwaAutoUpdate() {
|
|
9
20
|
const {
|
|
@@ -12,22 +23,51 @@ export function usePwaAutoUpdate() {
|
|
|
12
23
|
updateServiceWorker,
|
|
13
24
|
} = useRegisterSW();
|
|
14
25
|
|
|
26
|
+
const versionCheckedRef = useRef(false);
|
|
27
|
+
|
|
28
|
+
// Workbox detected a new service worker with updated assets
|
|
15
29
|
useEffect(() => {
|
|
16
|
-
// This effect triggers when a new service worker is ready to take over.
|
|
17
30
|
if (needRefresh) {
|
|
18
|
-
// This immediately tells the new service worker to take control,
|
|
19
|
-
// which will force a hard reload of the page.
|
|
20
31
|
updateServiceWorker(true);
|
|
21
32
|
}
|
|
22
|
-
|
|
23
|
-
// This effect triggers when the app's assets are fully cached and ready for offline use.
|
|
24
|
-
// We won't show a toast, but we'll log it for debugging purposes.
|
|
25
33
|
if (offlineReady) {
|
|
26
34
|
console.log('PWA is ready for offline use.');
|
|
27
|
-
// Reset the state to prevent this log from firing on every render.
|
|
28
35
|
setOfflineReady(false);
|
|
29
36
|
}
|
|
30
37
|
}, [needRefresh, offlineReady, setOfflineReady, updateServiceWorker]);
|
|
31
38
|
|
|
32
|
-
//
|
|
39
|
+
// Backend version mismatch check
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (versionCheckedRef.current) return;
|
|
42
|
+
|
|
43
|
+
const serverVersion = (window as any).plutonVersion;
|
|
44
|
+
if (!serverVersion || serverVersion === 'unknown') return;
|
|
45
|
+
|
|
46
|
+
const storedVersion = localStorage.getItem(VERSION_STORAGE_KEY);
|
|
47
|
+
|
|
48
|
+
if (storedVersion && storedVersion !== serverVersion) {
|
|
49
|
+
versionCheckedRef.current = true;
|
|
50
|
+
clearCachesAndReload(serverVersion);
|
|
51
|
+
} else if (!storedVersion) {
|
|
52
|
+
localStorage.setItem(VERSION_STORAGE_KEY, serverVersion);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function clearCachesAndReload(newVersion: string) {
|
|
58
|
+
try {
|
|
59
|
+
if ('serviceWorker' in navigator) {
|
|
60
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
61
|
+
await Promise.all(registrations.map((r) => r.unregister()));
|
|
62
|
+
}
|
|
63
|
+
if ('caches' in window) {
|
|
64
|
+
const cacheNames = await caches.keys();
|
|
65
|
+
await Promise.all(cacheNames.map((name) => caches.delete(name)));
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error('Failed to clear caches:', e);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
localStorage.setItem(VERSION_STORAGE_KEY, newVersion);
|
|
72
|
+
window.location.reload();
|
|
33
73
|
}
|
package/src/router.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import Plans from './routes/Plans/Plans';
|
|
|
3
3
|
import Login from './routes/Login/Login';
|
|
4
4
|
import Setup from './routes/Setup/Setup';
|
|
5
5
|
import { useAuth } from './services/users';
|
|
6
|
-
import { useSetupStatus } from './services/settings';
|
|
7
6
|
import App from './components/App/App/App';
|
|
8
7
|
import Settings from './routes/Settings/Settings';
|
|
9
8
|
import Storages from './routes/Storages/Storages';
|
|
@@ -14,14 +13,10 @@ import Footer from './components/App/Footer/Footer';
|
|
|
14
13
|
import NotFoundRoute from './routes/NotFoundRoute/NotFoundRoute';
|
|
15
14
|
import Icon from './components/common/Icon/Icon';
|
|
16
15
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
// and tells us if we're in binary mode and if setup is pending
|
|
20
|
-
const { data: setupStatus, isLoading: setupLoading } = useSetupStatus();
|
|
21
|
-
const { data: authData, isError: authError, isLoading: authLoading } = useAuth();
|
|
16
|
+
function PublicRoute({ children }: { children: React.ReactNode }) {
|
|
17
|
+
const { data: authData, isLoading: authLoading } = useAuth();
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
if (setupLoading) {
|
|
19
|
+
if (authLoading) {
|
|
25
20
|
return (
|
|
26
21
|
<div className="loadingScreen">
|
|
27
22
|
<Icon size={60} type="loading" />
|
|
@@ -29,16 +24,16 @@ function ProtectedLayout() {
|
|
|
29
24
|
);
|
|
30
25
|
}
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// For binary installations with pending setup, redirect to setup page
|
|
36
|
-
// This check happens BEFORE auth check so unauthenticated users can access setup
|
|
37
|
-
if (setupStatus?.data?.isBinary && setupStatus?.data?.setupPending) {
|
|
38
|
-
return <Navigate to="/setup" replace />;
|
|
27
|
+
if (authData) {
|
|
28
|
+
return <Navigate to="/" replace />;
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
return <>{children}</>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ProtectedLayout() {
|
|
35
|
+
const { data: authData, isError: authError, isLoading: authLoading } = useAuth();
|
|
36
|
+
|
|
42
37
|
if (authLoading) {
|
|
43
38
|
return (
|
|
44
39
|
<div className="loadingScreen">
|
|
@@ -47,6 +42,13 @@ function ProtectedLayout() {
|
|
|
47
42
|
);
|
|
48
43
|
}
|
|
49
44
|
|
|
45
|
+
// Check setup-pending from auth response headers (set by versionMiddleware on all responses)
|
|
46
|
+
const installType = (window as any).plutonInstallType;
|
|
47
|
+
const setupPending = (window as any).plutonSetupPending;
|
|
48
|
+
if (installType === 'binary' && setupPending) {
|
|
49
|
+
return <Navigate to="/setup" replace />;
|
|
50
|
+
}
|
|
51
|
+
|
|
50
52
|
// If auth failed, redirect to login
|
|
51
53
|
if (authError) {
|
|
52
54
|
return <Navigate to="/login" replace />;
|
|
@@ -74,7 +76,14 @@ export function AppRoutes() {
|
|
|
74
76
|
<Route path={'plan/:id'} element={<PlanSingle />} />
|
|
75
77
|
<Route path="*" element={<NotFoundRoute />} />
|
|
76
78
|
</Route>
|
|
77
|
-
<Route
|
|
79
|
+
<Route
|
|
80
|
+
path="login"
|
|
81
|
+
element={
|
|
82
|
+
<PublicRoute>
|
|
83
|
+
<Login />
|
|
84
|
+
</PublicRoute>
|
|
85
|
+
}
|
|
86
|
+
/>
|
|
78
87
|
<Route path="setup" element={<Setup />} />
|
|
79
88
|
</Routes>
|
|
80
89
|
);
|
|
@@ -6,10 +6,9 @@ import { useGetDevice, useGetSystemMetrics } from '../../services/devices';
|
|
|
6
6
|
import DeviceInfo from '../../components/Device/DeviceInfo/DeviceInfo';
|
|
7
7
|
import EditDevice from '../../components/Device/EditDevice/EditDevice';
|
|
8
8
|
import classes from './DeviceSingle.module.scss';
|
|
9
|
-
// import { compareVersions } from '../../utils/helpers';
|
|
10
9
|
import NotFound from '../../components/common/NotFound/NotFound';
|
|
11
10
|
import DeviceBackups from '../../components/Device/DeviceBackups/DeviceBackups';
|
|
12
|
-
import { DevicePlan } from '../../@types/devices';
|
|
11
|
+
import { DevicePlan, DeviceStorage } from '../../@types/devices';
|
|
13
12
|
|
|
14
13
|
const DeviceSingle = () => {
|
|
15
14
|
const { id } = useParams();
|
|
@@ -27,6 +26,7 @@ const DeviceSingle = () => {
|
|
|
27
26
|
const device = data?.result?.device;
|
|
28
27
|
const metrics = metricsData?.result && metricsData?.result?.system ? metricsData.result : device?.metrics;
|
|
29
28
|
const devicePlans: DevicePlan[] = data?.result?.plans || [];
|
|
29
|
+
const deviceStorages: DeviceStorage[] = data?.result?.storages || [];
|
|
30
30
|
const deviceName = device?.name;
|
|
31
31
|
const isPending = device?.status && device?.status === 'pending';
|
|
32
32
|
|
|
@@ -73,7 +73,7 @@ const DeviceSingle = () => {
|
|
|
73
73
|
</div>
|
|
74
74
|
) : (
|
|
75
75
|
<>
|
|
76
|
-
<DeviceBackups plans={devicePlans} />
|
|
76
|
+
<DeviceBackups plans={devicePlans} storages={deviceStorages} />
|
|
77
77
|
{metrics?.system && <DeviceInfo metrics={metrics} isRefetching={false} />}
|
|
78
78
|
</>
|
|
79
79
|
)}
|
|
@@ -14,9 +14,11 @@ import PlanUnlockModal from '../../components/Plan/PlanUnlockModal/PlanUnlockMod
|
|
|
14
14
|
import PlanRemoveModal from '../../components/Plan/PlanRemoveModal/PlanRemoveModal';
|
|
15
15
|
import PlanProgress from '../../components/Plan/PlanProgress/PlanProgress';
|
|
16
16
|
import PlanBackups from '../../components/Plan/PlanBackups/PlanBackups';
|
|
17
|
+
import PlanIntegrity from '../../components/Plan/PlanIntegrity/PlanIntegrity';
|
|
17
18
|
|
|
18
19
|
const PlanSingle = () => {
|
|
19
20
|
const [showMoreOptions, setShowMoreOptions] = useState(false);
|
|
21
|
+
const [showIntegrityModal, setShowIntegrityModal] = useState(false);
|
|
20
22
|
const { id } = useParams();
|
|
21
23
|
|
|
22
24
|
const EditPlanModal = useComponentOverride('EditPlan', EditPlan);
|
|
@@ -126,6 +128,15 @@ const PlanSingle = () => {
|
|
|
126
128
|
<Icon size={14} type="unlock" /> Unlock
|
|
127
129
|
</li>
|
|
128
130
|
)}
|
|
131
|
+
<li
|
|
132
|
+
className={classes.actionBtn}
|
|
133
|
+
onClick={() => {
|
|
134
|
+
setShowIntegrityModal(true);
|
|
135
|
+
setShowMoreOptions(false);
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<Icon size={14} type="integrity" /> Check Integrity
|
|
139
|
+
</li>
|
|
129
140
|
<li
|
|
130
141
|
className={classes.actionBtn}
|
|
131
142
|
onClick={() => {
|
|
@@ -174,6 +185,16 @@ const PlanSingle = () => {
|
|
|
174
185
|
close={() => setShowPruneModal(false)}
|
|
175
186
|
/>
|
|
176
187
|
)}
|
|
188
|
+
{showIntegrityModal && !isSync && (
|
|
189
|
+
<PlanIntegrity
|
|
190
|
+
planId={plan.id}
|
|
191
|
+
taskPending={taskPending}
|
|
192
|
+
verificationData={plan.verified}
|
|
193
|
+
storage={plan.storage}
|
|
194
|
+
replicationStorages={plan.settings.replication?.enabled ? plan.settings.replication.storages : []}
|
|
195
|
+
onClose={() => setShowIntegrityModal(false)}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
177
198
|
{showDeleteModal && (
|
|
178
199
|
<PlanRemoveModal planId={id} taskPending={taskPending} actionInProgress={actionInProgress} close={() => setShowDeleteModal(false)} />
|
|
179
200
|
)}
|
package/src/services/backups.ts
CHANGED
|
@@ -5,14 +5,12 @@ import { API_URL } from '../utils/constants';
|
|
|
5
5
|
const notifiedBackupProgress = new Set<string>();
|
|
6
6
|
|
|
7
7
|
// Generate Download
|
|
8
|
-
export async function generateBackupDownload({ backupId }: { backupId: string; planId: string }) {
|
|
9
|
-
|
|
10
|
-
const res = await fetch(
|
|
8
|
+
export async function generateBackupDownload({ backupId, replicationId }: { backupId: string; planId: string; replicationId?: string }) {
|
|
9
|
+
const url = `${API_URL}/backups/${backupId}/action/download?${replicationId ? `replicationId=${replicationId}` : ''}`;
|
|
10
|
+
const res = await fetch(url, {
|
|
11
11
|
method: 'POST',
|
|
12
12
|
credentials: 'include',
|
|
13
|
-
// headers: header,
|
|
14
13
|
});
|
|
15
|
-
// Check if response is ok
|
|
16
14
|
const data = await res.json();
|
|
17
15
|
if (!data.success) {
|
|
18
16
|
throw new Error(data.error);
|
|
@@ -241,13 +239,12 @@ export function useUpdateBackup() {
|
|
|
241
239
|
}
|
|
242
240
|
|
|
243
241
|
// Get Snapshot Files
|
|
244
|
-
export async function getSnapshotFiles({ backupId }: { backupId: string }) {
|
|
245
|
-
const
|
|
242
|
+
export async function getSnapshotFiles({ backupId, replicationId }: { backupId: string; replicationId?: string }) {
|
|
243
|
+
const url = replicationId ? `${API_URL}/backups/${backupId}/files?replicationId=${replicationId}` : `${API_URL}/backups/${backupId}/files`;
|
|
244
|
+
const res = await fetch(url, {
|
|
246
245
|
method: 'GET',
|
|
247
246
|
credentials: 'include',
|
|
248
|
-
// headers: header,
|
|
249
247
|
});
|
|
250
|
-
// Check if response is ok
|
|
251
248
|
const data = await res.json();
|
|
252
249
|
if (!data.success) {
|
|
253
250
|
throw new Error(data.error);
|
|
@@ -263,3 +260,29 @@ export function useGetSnapshotFiles(payload: { backupId: string }) {
|
|
|
263
260
|
retry: false,
|
|
264
261
|
});
|
|
265
262
|
}
|
|
263
|
+
|
|
264
|
+
// Retry Failed Replications
|
|
265
|
+
export async function retryFailedReplications({ backupId, replicationId }: { backupId: string; planId: string; replicationId: string }) {
|
|
266
|
+
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
267
|
+
const res = await fetch(`${API_URL}/backups/${backupId}/action/replication-retry?replicationId=${replicationId}`, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
credentials: 'include',
|
|
270
|
+
headers: header,
|
|
271
|
+
});
|
|
272
|
+
const data = await res.json();
|
|
273
|
+
if (!data.success) {
|
|
274
|
+
throw new Error(data.error);
|
|
275
|
+
}
|
|
276
|
+
return data;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function useRetryFailedReplications() {
|
|
280
|
+
const queryClient = useQueryClient();
|
|
281
|
+
return useMutation({
|
|
282
|
+
mutationFn: retryFailedReplications,
|
|
283
|
+
onSuccess: (res, payload) => {
|
|
284
|
+
console.log('res :', payload, res);
|
|
285
|
+
queryClient.invalidateQueries({ queryKey: ['plan', payload.planId] });
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|