@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
|
@@ -6,16 +6,20 @@ import classes from './RestoreWizard.module.scss';
|
|
|
6
6
|
import FolderPicker from '../../common/FolderPicker/FolderPicker';
|
|
7
7
|
import { RestoreSettings } from '../../../@types/restores';
|
|
8
8
|
import Toggle from '../../common/form/Toggle/Toggle';
|
|
9
|
+
import { Backup } from '../../..';
|
|
9
10
|
|
|
10
|
-
interface
|
|
11
|
+
interface RestoreSettingsStepProps {
|
|
11
12
|
backupId: string;
|
|
13
|
+
deviceId: string;
|
|
12
14
|
settings: RestoreSettings;
|
|
13
|
-
|
|
15
|
+
mirrors?: Backup['mirrors'];
|
|
16
|
+
primaryStorage: { id: string; type: string; name: string };
|
|
17
|
+
updateSettings: (settings: RestoreSettingsStepProps['settings']) => void;
|
|
14
18
|
goNext: () => void;
|
|
15
19
|
close: () => void;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const
|
|
22
|
+
const RestoreSettingsStep = ({ settings, mirrors = [], primaryStorage, updateSettings, goNext, close, deviceId }: RestoreSettingsStepProps) => {
|
|
19
23
|
const [showFileManager, setShowFileManager] = useState(false);
|
|
20
24
|
const [showCustomPathError, setShowCustomPathError] = useState(false);
|
|
21
25
|
|
|
@@ -30,17 +34,38 @@ const settingsStep = ({ backupId, settings, updateSettings, goNext, close }: set
|
|
|
30
34
|
return (
|
|
31
35
|
<div className={classes.stepContent}>
|
|
32
36
|
<div className={classes.step}>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
{mirrors && mirrors.length > 0 && (
|
|
38
|
+
<div className={classes.settingBlock}>
|
|
39
|
+
<Select
|
|
40
|
+
customClasses={classes.storageSelect}
|
|
41
|
+
label="Select Storage to Restore From"
|
|
42
|
+
options={[
|
|
43
|
+
{
|
|
44
|
+
label: primaryStorage!.name + ' (Primary)',
|
|
45
|
+
value: 'primary',
|
|
46
|
+
image: <img src={`/providers/${primaryStorage.type}.png`} />,
|
|
47
|
+
},
|
|
48
|
+
...mirrors.map((m) => ({
|
|
49
|
+
label: m.storageName + ' (Mirror)',
|
|
50
|
+
value: m.replicationId,
|
|
51
|
+
image: <img src={`/providers/${m.storageType}.png`} />,
|
|
52
|
+
})),
|
|
53
|
+
]}
|
|
54
|
+
fieldValue={settings.replicationId || primaryStorage.id}
|
|
55
|
+
full={true}
|
|
56
|
+
onUpdate={(value) => updateSettings({ ...settings, replicationId: value === 'primary' ? undefined : value })}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
|
|
36
61
|
<div className={classes.settingBlock}>
|
|
37
62
|
<Select
|
|
38
|
-
label=""
|
|
63
|
+
label="Select where you want to restore the backup"
|
|
39
64
|
options={[
|
|
40
65
|
{ label: 'Restore to Original Path(s)', value: 'original' },
|
|
41
66
|
{ label: 'Restore to a Custom Path', value: 'custom' },
|
|
42
67
|
]}
|
|
43
|
-
fieldValue={settings.type || '
|
|
68
|
+
fieldValue={settings.type || 'original'}
|
|
44
69
|
onUpdate={(val: string) => updateSettings({ ...settings, type: val as 'original' | 'custom' })}
|
|
45
70
|
full={true}
|
|
46
71
|
/>
|
|
@@ -138,9 +163,7 @@ const settingsStep = ({ backupId, settings, updateSettings, goNext, close }: set
|
|
|
138
163
|
label="Delete Files that are not in the Snapshot from the Target Restore Path"
|
|
139
164
|
fieldValue={settings.delete}
|
|
140
165
|
inline={false}
|
|
141
|
-
onUpdate={(val) => {
|
|
142
|
-
updateSettings({ ...settings, delete: val });
|
|
143
|
-
}}
|
|
166
|
+
onUpdate={(val) => updateSettings({ ...settings, delete: val })}
|
|
144
167
|
/>
|
|
145
168
|
</div>
|
|
146
169
|
</div>
|
|
@@ -156,7 +179,7 @@ const settingsStep = ({ backupId, settings, updateSettings, goNext, close }: set
|
|
|
156
179
|
|
|
157
180
|
{showFileManager && (
|
|
158
181
|
<FolderPicker
|
|
159
|
-
deviceId={'main'}
|
|
182
|
+
deviceId={deviceId || 'main'}
|
|
160
183
|
title="Choose a Path to Restore"
|
|
161
184
|
footerText="Select a Path where you want to restore your backup"
|
|
162
185
|
selected={settings.path}
|
|
@@ -168,4 +191,4 @@ const settingsStep = ({ backupId, settings, updateSettings, goNext, close }: set
|
|
|
168
191
|
);
|
|
169
192
|
};
|
|
170
193
|
|
|
171
|
-
export default
|
|
194
|
+
export default RestoreSettingsStep;
|
|
@@ -356,6 +356,7 @@
|
|
|
356
356
|
padding: 12px;
|
|
357
357
|
cursor: pointer;
|
|
358
358
|
border-bottom: 1px solid var(--line-color);
|
|
359
|
+
transition: all 0.12s linear;
|
|
359
360
|
h5 {
|
|
360
361
|
font-size: 0.75rem;
|
|
361
362
|
font-weight: 600;
|
|
@@ -372,6 +373,9 @@
|
|
|
372
373
|
color: var(--primary-color);
|
|
373
374
|
}
|
|
374
375
|
}
|
|
376
|
+
&:hover {
|
|
377
|
+
background-color: var(--primary-color-light);
|
|
378
|
+
}
|
|
375
379
|
&:last-child {
|
|
376
380
|
border-bottom: none;
|
|
377
381
|
}
|
|
@@ -8,14 +8,18 @@ import RestoreConfirmStep from './RestoreConfirmStep';
|
|
|
8
8
|
import { RestoredFileItem, RestoredItemsStats, RestoreFileItem, RestoreSettings } from '../../../@types/restores';
|
|
9
9
|
import { useGetSnapshotFiles } from '../../../services/backups';
|
|
10
10
|
import RestoreFileSelectorStep from './RestoreFileSelectorStep';
|
|
11
|
+
import { Backup, Plan } from '../../..';
|
|
11
12
|
|
|
12
13
|
interface RestoreWizardProps {
|
|
13
14
|
backupId: string;
|
|
14
15
|
planId: string;
|
|
16
|
+
deviceId: string;
|
|
17
|
+
planStorage: Plan['storage'];
|
|
18
|
+
mirrors?: Backup['mirrors'];
|
|
15
19
|
close: () => void;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const RestoreWizard = ({ backupId, planId, close }: RestoreWizardProps) => {
|
|
22
|
+
const RestoreWizard = ({ backupId, planId, deviceId, mirrors, planStorage, close }: RestoreWizardProps) => {
|
|
19
23
|
const [step, setStep] = useState(1);
|
|
20
24
|
const [restoreSettings, setRestoreSettings] = useState<RestoreSettings>({
|
|
21
25
|
type: 'original',
|
|
@@ -24,6 +28,7 @@ const RestoreWizard = ({ backupId, planId, close }: RestoreWizardProps) => {
|
|
|
24
28
|
includes: [],
|
|
25
29
|
excludes: [],
|
|
26
30
|
delete: false,
|
|
31
|
+
storageId: undefined,
|
|
27
32
|
});
|
|
28
33
|
const [restorePreview, setRestorePreview] = useState<{ stats: RestoredItemsStats | null; files: RestoredFileItem[] }>({ stats: null, files: [] });
|
|
29
34
|
const isFetching = useIsFetching();
|
|
@@ -82,7 +87,10 @@ const RestoreWizard = ({ backupId, planId, close }: RestoreWizardProps) => {
|
|
|
82
87
|
{step === 1 && (
|
|
83
88
|
<RestoreSettingsStep
|
|
84
89
|
backupId={backupId}
|
|
90
|
+
deviceId={deviceId}
|
|
85
91
|
settings={restoreSettings}
|
|
92
|
+
primaryStorage={planStorage}
|
|
93
|
+
mirrors={mirrors}
|
|
86
94
|
updateSettings={(settings) => setRestoreSettings(settings)}
|
|
87
95
|
goNext={() => setStep(2)}
|
|
88
96
|
close={close}
|
|
@@ -55,7 +55,7 @@ const GeneralSettings = ({ settings, onUpdate }: GeneralSettingsProps) => {
|
|
|
55
55
|
{ label: 'Light', value: 'light' },
|
|
56
56
|
]}
|
|
57
57
|
onUpdate={(val: string) => handleThemeChange(val as 'auto' | 'light' | 'dark')}
|
|
58
|
-
inline={
|
|
58
|
+
inline={false}
|
|
59
59
|
/>
|
|
60
60
|
</div>
|
|
61
61
|
</div>
|
|
@@ -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
|
|
@@ -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
|
@@ -89,6 +89,12 @@ export { default as PlanTypeSettings } from './Plan/PlanSettings/PlanTypeSetting
|
|
|
89
89
|
export { default as PlanStats } from './Plan/PlanStats/PlanStats';
|
|
90
90
|
export { default as PlanUnlockModal } from './Plan/PlanUnlockModal/PlanUnlockModal';
|
|
91
91
|
|
|
92
|
+
// Mirror/Replication components
|
|
93
|
+
export { default as MirrorStatusBadge } from './Plan/Mirrors/MirrorStatusBadge';
|
|
94
|
+
export { default as MirrorStorageSelector } from './Plan/Mirrors/MirrorStorageSelector';
|
|
95
|
+
export { default as MirrorStorageSelectorModal } from './Plan/Mirrors/MirrorStorageSelectorModal';
|
|
96
|
+
export { default as PlanReplicationSettings } from './Plan/PlanSettings/PlanReplicationSettings';
|
|
97
|
+
|
|
92
98
|
// Restore components
|
|
93
99
|
export { default as PlanRestores } from './Plan/Restores/Restores';
|
|
94
100
|
export { default as RestoreChangeViewer } from './Restore/RestoreChangeViewer/RestoreChangeViewer';
|
|
@@ -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
|
)}
|
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
|
+
}
|
package/src/services/plans.ts
CHANGED
|
@@ -392,3 +392,48 @@ export function useCheckActiveBackupsOrRestore() {
|
|
|
392
392
|
// refetchOnMount: true,
|
|
393
393
|
});
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
// Remove Replication Storage
|
|
397
|
+
export async function deleteReplicationStorage({
|
|
398
|
+
planID,
|
|
399
|
+
storageID,
|
|
400
|
+
storagePath,
|
|
401
|
+
removeData,
|
|
402
|
+
replicationId,
|
|
403
|
+
}: {
|
|
404
|
+
planID: string;
|
|
405
|
+
storageID: string;
|
|
406
|
+
storagePath: string;
|
|
407
|
+
removeData: boolean;
|
|
408
|
+
replicationId?: string;
|
|
409
|
+
}) {
|
|
410
|
+
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
411
|
+
const res = await fetch(`${API_URL}/plans/${planID}/action/delete-replication-storage`, {
|
|
412
|
+
method: 'POST',
|
|
413
|
+
credentials: 'include',
|
|
414
|
+
headers: header,
|
|
415
|
+
body: JSON.stringify({ storageID, storagePath, removeData, replicationId }),
|
|
416
|
+
});
|
|
417
|
+
const data = await res.json();
|
|
418
|
+
if (!data.success) {
|
|
419
|
+
throw new Error(data.error);
|
|
420
|
+
}
|
|
421
|
+
return data;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function useDeleteReplicationStorage() {
|
|
425
|
+
const queryClient = useQueryClient();
|
|
426
|
+
return useMutation({
|
|
427
|
+
mutationFn: deleteReplicationStorage,
|
|
428
|
+
onError: (error: Error) => {
|
|
429
|
+
console.log('error :', error?.message);
|
|
430
|
+
toast.error(error.message || `Error removing replication storage.`);
|
|
431
|
+
},
|
|
432
|
+
onSuccess: (res, variables) => {
|
|
433
|
+
console.log('# Replication Storage Removed! :', res, variables);
|
|
434
|
+
queryClient.invalidateQueries({ queryKey: ['plan', variables.planID] });
|
|
435
|
+
queryClient.invalidateQueries({ queryKey: ['plans'] });
|
|
436
|
+
toast.success(res?.message || `Replication storage removed successfully!`, { autoClose: 5000 });
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
}
|
package/src/services/restores.ts
CHANGED
|
@@ -89,6 +89,8 @@ export async function restoreBackup({
|
|
|
89
89
|
includes,
|
|
90
90
|
excludes,
|
|
91
91
|
deleteOption,
|
|
92
|
+
storageId,
|
|
93
|
+
replicationId,
|
|
92
94
|
}: {
|
|
93
95
|
backupId: string;
|
|
94
96
|
planId: string;
|
|
@@ -97,13 +99,15 @@ export async function restoreBackup({
|
|
|
97
99
|
includes?: string[];
|
|
98
100
|
excludes?: string[];
|
|
99
101
|
deleteOption: boolean;
|
|
102
|
+
storageId?: string;
|
|
103
|
+
replicationId?: string;
|
|
100
104
|
}) {
|
|
101
105
|
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
102
106
|
const res = await fetch(`${API_URL}/restores/action/restore`, {
|
|
103
107
|
method: 'POST',
|
|
104
108
|
credentials: 'include',
|
|
105
109
|
headers: header,
|
|
106
|
-
body: JSON.stringify({ backupId, planId, target, overwrite, includes, excludes, delete: deleteOption }),
|
|
110
|
+
body: JSON.stringify({ backupId, planId, target, overwrite, includes, excludes, delete: deleteOption, storageId, replicationId }),
|
|
107
111
|
});
|
|
108
112
|
const data = await res.json();
|
|
109
113
|
if (!data.success) {
|
|
@@ -132,6 +136,8 @@ export async function getDryRestoreStats({
|
|
|
132
136
|
includes,
|
|
133
137
|
excludes,
|
|
134
138
|
deleteOption,
|
|
139
|
+
storageId,
|
|
140
|
+
replicationId,
|
|
135
141
|
}: {
|
|
136
142
|
backupId: string;
|
|
137
143
|
planId: string;
|
|
@@ -140,13 +146,15 @@ export async function getDryRestoreStats({
|
|
|
140
146
|
includes?: string[];
|
|
141
147
|
excludes?: string[];
|
|
142
148
|
deleteOption?: boolean;
|
|
149
|
+
storageId?: string;
|
|
150
|
+
replicationId?: string;
|
|
143
151
|
}) {
|
|
144
152
|
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
145
153
|
const res = await fetch(`${API_URL}/restores/action/dryrestore`, {
|
|
146
154
|
method: 'POST',
|
|
147
155
|
credentials: 'include',
|
|
148
156
|
headers: header,
|
|
149
|
-
body: JSON.stringify({ backupId, planId, target, overwrite, includes, excludes, delete: deleteOption }),
|
|
157
|
+
body: JSON.stringify({ backupId, planId, target, overwrite, includes, excludes, delete: deleteOption, storageId, replicationId }),
|
|
150
158
|
});
|
|
151
159
|
const data = await res.json();
|
|
152
160
|
if (!data.success) {
|
package/src/services/users.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { API_URL } from '../utils/constants';
|
|
2
|
-
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
3
|
import { useNavigate } from 'react-router';
|
|
4
4
|
|
|
5
5
|
interface LoginCredentials {
|
|
@@ -12,22 +12,28 @@ export type InstallType = 'docker' | 'binary' | 'dev';
|
|
|
12
12
|
//VALIDATE USER
|
|
13
13
|
export async function validateAuth() {
|
|
14
14
|
const res = await fetch(`${API_URL}/user/validate`, { method: 'GET', credentials: 'include' });
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
|
|
16
|
+
// Read headers before checking status - middleware sets these on all responses
|
|
18
17
|
const appVersion = res.headers.get('x-app-version');
|
|
19
18
|
const serverOS = res.headers.get('x-server-os');
|
|
20
19
|
const installType = (res.headers.get('x-install-type') || 'dev') as InstallType;
|
|
20
|
+
const setupPending = res.headers.get('x-setup-pending') === '1';
|
|
21
21
|
|
|
22
22
|
(window as any).plutonVersion = appVersion || 'unknown';
|
|
23
23
|
(window as any).plutonServerOS = serverOS || 'unknown';
|
|
24
24
|
(window as any).plutonInstallType = installType;
|
|
25
|
+
(window as any).plutonSetupPending = setupPending;
|
|
26
|
+
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error('Invalid authentication');
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
const data = await res.json();
|
|
27
32
|
return {
|
|
28
33
|
...data,
|
|
29
34
|
appVersion,
|
|
30
35
|
installType,
|
|
36
|
+
setupPending,
|
|
31
37
|
};
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -59,10 +65,11 @@ export async function loginUser(credentials: LoginCredentials) {
|
|
|
59
65
|
// Add this new hook
|
|
60
66
|
export function useLogin() {
|
|
61
67
|
const navigate = useNavigate();
|
|
68
|
+
const queryClient = useQueryClient();
|
|
62
69
|
return useMutation({
|
|
63
70
|
mutationFn: loginUser,
|
|
64
71
|
onSuccess: (res) => {
|
|
65
|
-
|
|
72
|
+
queryClient.removeQueries({ queryKey: ['auth'] });
|
|
66
73
|
if (res.totpRequired) {
|
|
67
74
|
navigate('/login/verify-otp');
|
|
68
75
|
} else {
|
|
@@ -87,9 +94,11 @@ export async function logoutUser() {
|
|
|
87
94
|
|
|
88
95
|
export function useLogout() {
|
|
89
96
|
const navigate = useNavigate();
|
|
97
|
+
const queryClient = useQueryClient();
|
|
90
98
|
return useMutation({
|
|
91
99
|
mutationFn: logoutUser,
|
|
92
100
|
onSuccess: () => {
|
|
101
|
+
queryClient.removeQueries({ queryKey: ['auth'] });
|
|
93
102
|
navigate('/login');
|
|
94
103
|
},
|
|
95
104
|
});
|