@plutonhq/core-frontend 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/@types/plans.d.ts +4 -0
- package/dist-lib/@types/plans.d.ts.map +1 -1
- package/dist-lib/components/Plan/AddPlan/AddPlan.d.ts.map +1 -1
- package/dist-lib/components/Plan/AddPlan/AddPlan.js +29 -24
- package/dist-lib/components/Plan/AddPlan/AddPlan.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js +33 -33
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.js.map +1 -1
- package/dist-lib/components/Plan/BackupEvents/BackupEvents.module.scss.js +36 -36
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.d.ts.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js +63 -53
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.js.map +1 -1
- package/dist-lib/components/Plan/BackupProgress/BackupProgress.module.scss.js +32 -32
- package/dist-lib/components/Plan/Backups/Backups.d.ts.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.js +148 -144
- package/dist-lib/components/Plan/Backups/Backups.js.map +1 -1
- package/dist-lib/components/Plan/Backups/Backups.module.scss.js +34 -32
- package/dist-lib/components/Plan/Backups/Backups.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts +9 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.d.ts.map +1 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.js +117 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.js.map +1 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js +20 -0
- package/dist-lib/components/Plan/FilterPlans/FilterPlans.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts +4 -2
- package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanForm/PlanForm.js +33 -29
- package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts +2 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js +85 -57
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.js.map +1 -1
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js +11 -9
- package/dist-lib/components/Plan/PlanIntegrity/PlanIntegrity.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.js +1 -1
- package/dist-lib/components/Plan/PlanItems/PlanItem.js.map +1 -1
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts +9 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.js +262 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.js.map +1 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js +14 -0
- package/dist-lib/components/Plan/PlanRepair/PlanRepair.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts +4 -2
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js +24 -22
- package/dist-lib/components/Plan/PlanSettings/PlanAdvancedSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts +4 -2
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js +39 -28
- package/dist-lib/components/Plan/PlanSettings/PlanGeneralSettings.js.map +1 -1
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +66 -64
- package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js.map +1 -1
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts +7 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.d.ts.map +1 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js +116 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.js.map +1 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js +20 -0
- package/dist-lib/components/Plan/PlanSizeChart/PlanSizeChart.module.scss.js.map +1 -0
- package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
- package/dist-lib/components/Plan/PlanStats/PlanStats.js +29 -30
- package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
- package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +16 -14
- package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js.map +1 -1
- package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
- package/dist-lib/components/common/Icon/Icon.js +395 -378
- package/dist-lib/components/common/Icon/Icon.js.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.d.ts +2 -1
- package/dist-lib/components/common/SortItems/SortItems.d.ts.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.js +14 -14
- package/dist-lib/components/common/SortItems/SortItems.js.map +1 -1
- package/dist-lib/components/common/SortItems/SortItems.module.scss.js +1 -1
- package/dist-lib/components/common/form/MultiSelect/MultiSelect.module.scss.js +16 -16
- package/dist-lib/components/index.d.ts +2 -0
- package/dist-lib/components/index.d.ts.map +1 -1
- package/dist-lib/components.js +199 -195
- package/dist-lib/components.js.map +1 -1
- package/dist-lib/hooks/usePlanSingleActions.d.ts.map +1 -1
- package/dist-lib/hooks/usePlanSingleActions.js +22 -19
- package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
- package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js +449 -0
- package/dist-lib/node_modules/.pnpm/@kurkle_color@0.3.4/node_modules/@kurkle/color/dist/color.esm.js.map +1 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js +5219 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chart.js.map +1 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js +1691 -0
- package/dist-lib/node_modules/.pnpm/chart.js@4.5.1/node_modules/chart.js/dist/chunks/helpers.dataset.js.map +1 -0
- package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js +93 -0
- package/dist-lib/node_modules/.pnpm/react-chartjs-2@5.3.1_chart.js@4.5.1_react@18.3.1/node_modules/react-chartjs-2/dist/index.js.map +1 -0
- package/dist-lib/routes/Login/Login.d.ts.map +1 -1
- package/dist-lib/routes/Login/Login.js +45 -36
- package/dist-lib/routes/Login/Login.js.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.d.ts.map +1 -1
- package/dist-lib/routes/PlanSingle/PlanSingle.js +131 -118
- package/dist-lib/routes/PlanSingle/PlanSingle.js.map +1 -1
- package/dist-lib/routes/Plans/Plans.d.ts.map +1 -1
- package/dist-lib/routes/Plans/Plans.js +77 -51
- package/dist-lib/routes/Plans/Plans.js.map +1 -1
- package/dist-lib/services/plans.d.ts +33 -5
- package/dist-lib/services/plans.d.ts.map +1 -1
- package/dist-lib/services/plans.js +92 -67
- package/dist-lib/services/plans.js.map +1 -1
- package/dist-lib/services.js +93 -91
- package/dist-lib/styles/core-frontend.css +1 -1
- package/dist-lib/styles/global.scss +4 -0
- package/dist-lib/utils/helpers.d.ts +2 -0
- package/dist-lib/utils/helpers.d.ts.map +1 -1
- package/dist-lib/utils/helpers.js +68 -42
- package/dist-lib/utils/helpers.js.map +1 -1
- package/dist-lib/utils.js +36 -34
- package/package.json +3 -1
- package/src/@types/plans.ts +5 -0
- package/src/components/Plan/AddPlan/AddPlan.tsx +22 -16
- package/src/components/Plan/BackupEvents/BackupEvents.module.scss +2 -0
- package/src/components/Plan/BackupEvents/BackupEvents.tsx +2 -2
- package/src/components/Plan/BackupProgress/BackupProgress.module.scss +1 -0
- package/src/components/Plan/BackupProgress/BackupProgress.tsx +7 -2
- package/src/components/Plan/Backups/Backups.module.scss +16 -0
- package/src/components/Plan/Backups/Backups.tsx +13 -2
- package/src/components/Plan/FilterPlans/FilterPlans.module.scss +65 -0
- package/src/components/Plan/FilterPlans/FilterPlans.tsx +126 -0
- package/src/components/Plan/PlanForm/PlanForm.tsx +7 -1
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.module.scss +19 -0
- package/src/components/Plan/PlanIntegrity/PlanIntegrity.tsx +40 -3
- package/src/components/Plan/PlanItems/PlanItem.tsx +1 -1
- package/src/components/Plan/PlanRepair/PlanRepair.module.scss +53 -0
- package/src/components/Plan/PlanRepair/PlanRepair.tsx +243 -0
- package/src/components/Plan/PlanSettings/PlanAdvancedSettings.tsx +6 -2
- package/src/components/Plan/PlanSettings/PlanGeneralSettings.tsx +14 -2
- package/src/components/Plan/PlanSettings/PlanSettings.module.scss +8 -0
- package/src/components/Plan/PlanSizeChart/PlanSizeChart.module.scss +76 -0
- package/src/components/Plan/PlanSizeChart/PlanSizeChart.tsx +163 -0
- package/src/components/Plan/PlanStats/PlanStats.module.scss +16 -2
- package/src/components/Plan/PlanStats/PlanStats.tsx +8 -11
- package/src/components/common/Icon/Icon.tsx +21 -0
- package/src/components/common/SortItems/SortItems.module.scss +3 -2
- package/src/components/common/SortItems/SortItems.tsx +6 -3
- package/src/components/common/form/MultiSelect/MultiSelect.module.scss +1 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/usePlanSingleActions.tsx +26 -23
- package/src/routes/Login/Login.tsx +8 -2
- package/src/routes/PlanSingle/PlanSingle.tsx +17 -0
- package/src/routes/Plans/Plans.tsx +70 -35
- package/src/services/plans.ts +40 -4
- package/src/styles/global.scss +4 -0
- package/src/utils/helpers.ts +25 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
span {
|
|
8
8
|
opacity: 1;
|
|
9
9
|
color: inherit;
|
|
10
|
+
font-weight: 600;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -18,11 +19,11 @@
|
|
|
18
19
|
background: var(--field-bg);
|
|
19
20
|
width: calc(100% - 2px);
|
|
20
21
|
border-radius: 0 0 4px 4px;
|
|
21
|
-
max-height: 200px;
|
|
22
22
|
overflow: auto;
|
|
23
23
|
font-size: 0.75rem;
|
|
24
24
|
right: 0;
|
|
25
|
-
width:
|
|
25
|
+
width: 210px;
|
|
26
|
+
max-height: 320px;
|
|
26
27
|
border-radius: 6px;
|
|
27
28
|
ul {
|
|
28
29
|
margin: 0;
|
|
@@ -2,18 +2,19 @@ import { useState } from 'react';
|
|
|
2
2
|
import Icon from '../Icon/Icon';
|
|
3
3
|
import classes from './SortItems.module.scss';
|
|
4
4
|
type SortItemsProps = {
|
|
5
|
+
id: string;
|
|
5
6
|
onSort: (s: string) => void;
|
|
6
7
|
options: { label: string; value: string }[];
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
const SortItems = ({ options, onSort }: SortItemsProps) => {
|
|
10
|
+
const SortItems = ({ id, options, onSort }: SortItemsProps) => {
|
|
10
11
|
const [showDropDown, setshowDropDown] = useState(false);
|
|
11
|
-
const [selected, setsSelected] = useState('');
|
|
12
|
+
const [selected, setsSelected] = useState(localStorage.getItem(id) || '');
|
|
12
13
|
const selectedLabel = options.find((item) => item.value === selected);
|
|
13
14
|
return (
|
|
14
15
|
<div className={classes.sortItems}>
|
|
15
16
|
<button
|
|
16
|
-
className={selected ? classes.sortActive : ''}
|
|
17
|
+
className={selected || showDropDown ? classes.sortActive : ''}
|
|
17
18
|
onClick={() => setshowDropDown(!showDropDown)}
|
|
18
19
|
data-tooltip-id="appTooltip"
|
|
19
20
|
data-tooltip-content="Sort"
|
|
@@ -34,6 +35,7 @@ const SortItems = ({ options, onSort }: SortItemsProps) => {
|
|
|
34
35
|
onClick={() => {
|
|
35
36
|
setsSelected(item.value);
|
|
36
37
|
onSort(item.value);
|
|
38
|
+
localStorage.setItem(id, item.value);
|
|
37
39
|
setshowDropDown(false);
|
|
38
40
|
}}
|
|
39
41
|
className={`${selected && item.value === selected ? classes.selectedItem : ''}`}
|
|
@@ -46,6 +48,7 @@ const SortItems = ({ options, onSort }: SortItemsProps) => {
|
|
|
46
48
|
onClick={() => {
|
|
47
49
|
setsSelected('');
|
|
48
50
|
onSort('');
|
|
51
|
+
localStorage.removeItem(id);
|
|
49
52
|
setshowDropDown(false);
|
|
50
53
|
}}
|
|
51
54
|
>
|
package/src/components/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { default as PathPicker } from './common/PathPicker/PathPicker';
|
|
|
27
27
|
export { default as SearchItems } from './common/SearchItems/SearchItems';
|
|
28
28
|
export { default as SidePanel } from './common/SidePanel/SidePanel';
|
|
29
29
|
export { default as SortItems } from './common/SortItems/SortItems';
|
|
30
|
+
export { default as FilterPlans } from './Plan/FilterPlans/FilterPlans';
|
|
30
31
|
export { default as StatusLabel } from './common/StatusLabel/StatusLabel';
|
|
31
32
|
export { default as Tabs, TabList, TabPanel, Tab } from './common/Tabs/Tabs';
|
|
32
33
|
export { default as TagsFilter } from './common/TagsFilter/TagsFilter';
|
|
@@ -90,6 +91,7 @@ export { default as PlanTypeSettings } from './Plan/PlanSettings/PlanTypeSetting
|
|
|
90
91
|
export { default as PlanStats } from './Plan/PlanStats/PlanStats';
|
|
91
92
|
export { default as PlanUnlockModal } from './Plan/PlanUnlockModal/PlanUnlockModal';
|
|
92
93
|
export { default as PlanIntegrity } from './Plan/PlanIntegrity/PlanIntegrity';
|
|
94
|
+
export { default as PlanRepair } from './Plan/PlanRepair/PlanRepair';
|
|
93
95
|
export { default as SnapshotViewer } from './Plan/SnapshotViewer/SnapshotViewer';
|
|
94
96
|
export { default as SnapshotViewerFile } from './Plan/SnapshotViewer/SnapshotViewerFile';
|
|
95
97
|
|
|
@@ -97,30 +97,33 @@ export const usePlanSingleActions = (): {
|
|
|
97
97
|
|
|
98
98
|
const toastId = toast.loading(`Starting ${isSync ? 'Sync' : 'Backup'}...`);
|
|
99
99
|
|
|
100
|
-
performBackupMutation.mutate(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
100
|
+
performBackupMutation.mutate(
|
|
101
|
+
{ id: plan.id },
|
|
102
|
+
{
|
|
103
|
+
onSuccess: (data) => {
|
|
104
|
+
const msg = data?.message || `${isSync ? 'Sync' : 'Backup'} initiated successfully! 🚀`;
|
|
105
|
+
const notStarted = !isSync && data?.message && data?.message.includes('reached the concurrency limit');
|
|
106
|
+
toast.update(toastId, {
|
|
107
|
+
render: isSync ? msg : notStarted ? data?.message : 'Backup initiated successfully!',
|
|
108
|
+
type: 'success',
|
|
109
|
+
isLoading: false,
|
|
110
|
+
autoClose: 3000,
|
|
111
|
+
});
|
|
112
|
+
if (!isSync && !notStarted) {
|
|
113
|
+
navigate(`/plan/${plan.id}?pendingbackup=1`);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
onError: (error: any) => {
|
|
117
|
+
toast.update(toastId, {
|
|
118
|
+
render: `${isSync ? 'Sync' : 'Backup'} failed to start. ${error?.message || 'Unknown Error.'}`,
|
|
119
|
+
type: 'error',
|
|
120
|
+
isLoading: false,
|
|
121
|
+
autoClose: false,
|
|
122
|
+
closeButton: true,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
122
125
|
},
|
|
123
|
-
|
|
126
|
+
);
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
return {
|
|
@@ -65,7 +65,13 @@ const Login = () => {
|
|
|
65
65
|
</h3>
|
|
66
66
|
</div>
|
|
67
67
|
<div className={classes.container}>
|
|
68
|
-
<
|
|
68
|
+
<form
|
|
69
|
+
className="loginForm"
|
|
70
|
+
onSubmit={(e) => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
handleLogin();
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
69
75
|
<div className={classes.loginInput}>
|
|
70
76
|
<Icon type="user" classes={classes.loginInputIcon} />
|
|
71
77
|
<input
|
|
@@ -90,7 +96,7 @@ const Login = () => {
|
|
|
90
96
|
{loginMutation.isPending ? 'Logging in...' : 'Login'}
|
|
91
97
|
</button>
|
|
92
98
|
{error && error.msg && <div className={classes.loginErrorMsg}>{error.msg}</div>}
|
|
93
|
-
</
|
|
99
|
+
</form>
|
|
94
100
|
</div>
|
|
95
101
|
</div>
|
|
96
102
|
);
|
|
@@ -15,10 +15,13 @@ import PlanRemoveModal from '../../components/Plan/PlanRemoveModal/PlanRemoveMod
|
|
|
15
15
|
import PlanProgress from '../../components/Plan/PlanProgress/PlanProgress';
|
|
16
16
|
import PlanBackups from '../../components/Plan/PlanBackups/PlanBackups';
|
|
17
17
|
import PlanIntegrity from '../../components/Plan/PlanIntegrity/PlanIntegrity';
|
|
18
|
+
import PlanRepair from '../../components/Plan/PlanRepair/PlanRepair';
|
|
19
|
+
import { PlanVerifiedResult } from '../..';
|
|
18
20
|
|
|
19
21
|
const PlanSingle = () => {
|
|
20
22
|
const [showMoreOptions, setShowMoreOptions] = useState(false);
|
|
21
23
|
const [showIntegrityModal, setShowIntegrityModal] = useState(false);
|
|
24
|
+
const [showRepoRepair, setShowRepoRepair] = useState('');
|
|
22
25
|
const { id } = useParams();
|
|
23
26
|
|
|
24
27
|
const EditPlanModal = useComponentOverride('EditPlan', EditPlan);
|
|
@@ -198,6 +201,20 @@ const PlanSingle = () => {
|
|
|
198
201
|
storage={plan.storage}
|
|
199
202
|
replicationStorages={plan.settings.replication?.enabled ? plan.settings.replication.storages : []}
|
|
200
203
|
onClose={() => setShowIntegrityModal(false)}
|
|
204
|
+
onRepairOpen={(replicationId) => {
|
|
205
|
+
setShowIntegrityModal(false);
|
|
206
|
+
setShowRepoRepair(replicationId);
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
)}
|
|
210
|
+
{showRepoRepair && !isSync && (
|
|
211
|
+
<PlanRepair
|
|
212
|
+
planId={id}
|
|
213
|
+
errorType={
|
|
214
|
+
(plan.verified?.result[showRepoRepair as keyof typeof plan.verified.result] as PlanVerifiedResult)?.errorType || 'unknown'
|
|
215
|
+
}
|
|
216
|
+
onClose={() => setShowRepoRepair('')}
|
|
217
|
+
onOpenIntegrity={() => setShowIntegrityModal(true)}
|
|
201
218
|
/>
|
|
202
219
|
)}
|
|
203
220
|
{showDeleteModal && (
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
import classes from './Plans.module.scss';
|
|
3
3
|
import PageHeader from '../../components/common/PageHeader/PageHeader';
|
|
4
4
|
import PlanItem from '../../components/Plan/PlanItems/PlanItem';
|
|
5
5
|
import AddPlan from '../../components/Plan/AddPlan/AddPlan';
|
|
6
|
-
import TagsFilter from '../../components/common/TagsFilter/TagsFilter';
|
|
7
6
|
import SearchItems from '../../components/common/SearchItems/SearchItems';
|
|
8
7
|
import SortItems from '../../components/common/SortItems/SortItems';
|
|
9
8
|
import { useGetPlans } from '../../services/plans';
|
|
@@ -11,14 +10,14 @@ import { Plan } from '../../@types/plans';
|
|
|
11
10
|
import { useComponentOverride } from '../../context/ComponentOverrideContext';
|
|
12
11
|
import ItemsLayout from '../../components/common/ItemsLayout/ItemsLayout';
|
|
13
12
|
import SkeletonItems from '../../components/Skeleton/SkeletonItems';
|
|
13
|
+
import { getIntervalMinutes } from '../../utils';
|
|
14
|
+
import FilterPlans, { PlanFilterTypes } from '../../components/Plan/FilterPlans/FilterPlans';
|
|
14
15
|
|
|
15
16
|
const Plans = () => {
|
|
16
17
|
const [showSidePanel, setShowSidePanel] = useState(false);
|
|
17
18
|
const [plans, setPlans] = useState<Plan[]>([]);
|
|
18
19
|
const [plansLayout, setPlansLayout] = useState<'list' | 'grid'>(() => (localStorage.getItem('plans_layout') === 'grid' ? 'grid' : 'list'));
|
|
19
20
|
const { data, isLoading } = useGetPlans();
|
|
20
|
-
// const data = { result: [] };
|
|
21
|
-
// const isLoading = true;
|
|
22
21
|
|
|
23
22
|
const fetchedPlans: Plan[] = data?.result || [];
|
|
24
23
|
|
|
@@ -27,33 +26,19 @@ const Plans = () => {
|
|
|
27
26
|
useEffect(() => {
|
|
28
27
|
if (data?.result) {
|
|
29
28
|
setPlans(data.result);
|
|
29
|
+
const sortSetting = localStorage.getItem('plans_sort');
|
|
30
|
+
const filterSettingsRaw = localStorage.getItem('plans_filter');
|
|
31
|
+
const filterSettings = filterSettingsRaw ? JSON.parse(filterSettingsRaw) : null;
|
|
32
|
+
if (filterSettings) {
|
|
33
|
+
applyFilters(filterSettings, data.result);
|
|
34
|
+
}
|
|
35
|
+
if (sortSetting) {
|
|
36
|
+
sortPlans(sortSetting, data.result);
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
}, [data]);
|
|
32
40
|
|
|
33
|
-
console.log('plans :', plans);
|
|
34
|
-
|
|
35
|
-
const allTags = useMemo(() => {
|
|
36
|
-
const tags: string[] = [];
|
|
37
|
-
if (plans && plans.length > 0) {
|
|
38
|
-
plans.forEach((plan) => {
|
|
39
|
-
plan.tags.forEach((tag) => {
|
|
40
|
-
if (!tags.includes(tag)) {
|
|
41
|
-
tags.push(tag);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return tags;
|
|
48
|
-
}, [plans]);
|
|
49
|
-
|
|
50
|
-
const filterByTags = (tag: string) => {
|
|
51
|
-
if (tag) {
|
|
52
|
-
setPlans(fetchedPlans.filter((p) => p.tags.includes(tag)));
|
|
53
|
-
} else {
|
|
54
|
-
setPlans(fetchedPlans);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
41
|
+
// console.log('plans :', plans);
|
|
57
42
|
|
|
58
43
|
const searchPlans = (term: string) => {
|
|
59
44
|
if (term) {
|
|
@@ -63,28 +48,73 @@ const Plans = () => {
|
|
|
63
48
|
}
|
|
64
49
|
};
|
|
65
50
|
|
|
66
|
-
const sortPlans = (sortBy: string) => {
|
|
51
|
+
const sortPlans = (sortBy: string, plansToSort?: Plan[]) => {
|
|
52
|
+
const thePlans = plansToSort && plansToSort.length > 0 ? plansToSort : plans;
|
|
67
53
|
switch (sortBy) {
|
|
68
54
|
case 'title_asc':
|
|
69
|
-
setPlans([...
|
|
55
|
+
setPlans([...thePlans].sort((a, b) => a.title.localeCompare(b.title)));
|
|
70
56
|
break;
|
|
71
57
|
case 'title_desc':
|
|
72
|
-
setPlans([...
|
|
58
|
+
setPlans([...thePlans].sort((a, b) => b.title.localeCompare(a.title)));
|
|
73
59
|
break;
|
|
74
60
|
case 'created_asc':
|
|
75
|
-
setPlans([...
|
|
61
|
+
setPlans([...thePlans].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt)));
|
|
76
62
|
break;
|
|
77
63
|
case 'created_desc':
|
|
78
|
-
setPlans([...
|
|
64
|
+
setPlans([...thePlans].sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)));
|
|
65
|
+
break;
|
|
66
|
+
case 'size_asc':
|
|
67
|
+
setPlans([...thePlans].sort((a, b) => a.stats.size - b.stats.size));
|
|
68
|
+
break;
|
|
69
|
+
case 'size_desc':
|
|
70
|
+
setPlans([...thePlans].sort((a, b) => b.stats.size - a.stats.size));
|
|
71
|
+
break;
|
|
72
|
+
case 'run_frequency_asc':
|
|
73
|
+
setPlans([...thePlans].sort((a, b) => getIntervalMinutes(a.settings.interval) - getIntervalMinutes(b.settings.interval)));
|
|
74
|
+
break;
|
|
75
|
+
case 'run_frequency_desc':
|
|
76
|
+
setPlans([...thePlans].sort((a, b) => getIntervalMinutes(b.settings.interval) - getIntervalMinutes(a.settings.interval)));
|
|
79
77
|
break;
|
|
80
78
|
case '':
|
|
81
|
-
setPlans([...
|
|
79
|
+
setPlans([...fetchedPlans]);
|
|
82
80
|
break;
|
|
83
81
|
default:
|
|
84
82
|
break;
|
|
85
83
|
}
|
|
86
84
|
};
|
|
87
85
|
|
|
86
|
+
const applyFilters = (filter: Record<PlanFilterTypes, string[]> | null, plansToSort?: Plan[]) => {
|
|
87
|
+
if (!filter) {
|
|
88
|
+
setPlans(fetchedPlans);
|
|
89
|
+
const sortSetting = localStorage.getItem('plans_sort');
|
|
90
|
+
if (sortSetting) {
|
|
91
|
+
sortPlans(sortSetting, fetchedPlans);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
} else {
|
|
95
|
+
let filteredPlans = plansToSort && plansToSort.length > 0 ? plansToSort : plans;
|
|
96
|
+
|
|
97
|
+
if (filter.devices.length > 0) {
|
|
98
|
+
filteredPlans = filteredPlans.filter((plan) => filter.devices.includes(plan.device.id));
|
|
99
|
+
}
|
|
100
|
+
if (filter.methods.length > 0) {
|
|
101
|
+
filteredPlans = filteredPlans.filter((plan) => filter.methods.includes(plan.method));
|
|
102
|
+
}
|
|
103
|
+
if (filter.storages.length > 0) {
|
|
104
|
+
filteredPlans = filteredPlans.filter((plan) => filter.storages.includes(plan.storage.id));
|
|
105
|
+
}
|
|
106
|
+
if (filter.tags.length > 0) {
|
|
107
|
+
filteredPlans = filteredPlans.filter((plan) => plan.tags.some((tag) => filter.tags.includes(tag)));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setPlans(filteredPlans);
|
|
111
|
+
const sortSetting = localStorage.getItem('plans_sort');
|
|
112
|
+
if (sortSetting) {
|
|
113
|
+
sortPlans(sortSetting, filteredPlans);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
88
118
|
return (
|
|
89
119
|
<div className={classes.plans}>
|
|
90
120
|
<PageHeader
|
|
@@ -94,17 +124,22 @@ const Plans = () => {
|
|
|
94
124
|
buttonAction={() => setShowSidePanel(true)}
|
|
95
125
|
rightSection={
|
|
96
126
|
<>
|
|
97
|
-
<TagsFilter tags={allTags} onSelect={filterByTags} />
|
|
98
127
|
<SearchItems onSearch={(term) => searchPlans(term)} itemName="Plans" />
|
|
99
128
|
<SortItems
|
|
129
|
+
id={'plans_sort'}
|
|
100
130
|
options={[
|
|
101
131
|
{ label: 'Title (A to Z)', value: 'title_asc' },
|
|
102
132
|
{ label: 'Title (Z to A)', value: 'title_desc' },
|
|
133
|
+
{ label: 'Size (Smallest First)', value: 'size_asc' },
|
|
134
|
+
{ label: 'Size (Largest First)', value: 'size_desc' },
|
|
103
135
|
{ label: 'Date Created (Oldest First)', value: 'created_asc' },
|
|
104
136
|
{ label: 'Date Created (Newest First)', value: 'created_desc' },
|
|
137
|
+
{ label: 'Run Frequency (Shortest First)', value: 'run_frequency_asc' },
|
|
138
|
+
{ label: 'Run Frequency (Longest First)', value: 'run_frequency_desc' },
|
|
105
139
|
]}
|
|
106
140
|
onSort={(sortBy) => sortPlans(sortBy)}
|
|
107
141
|
/>
|
|
142
|
+
<FilterPlans plans={fetchedPlans} onUpdate={(filterSettings) => applyFilters(filterSettings)} />
|
|
108
143
|
<ItemsLayout onChange={(value) => setPlansLayout(value)} type="plans" value={plansLayout} />
|
|
109
144
|
</>
|
|
110
145
|
}
|
package/src/services/plans.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { toast } from 'react-toastify';
|
|
3
3
|
import { API_URL } from '../utils/constants';
|
|
4
|
-
import { NewPlanSettings, Plan, PlanNotification } from '../@types/plans';
|
|
4
|
+
import { NewPlanSettings, Plan, PlanAddRunSettings, PlanNotification } from '../@types/plans';
|
|
5
|
+
|
|
6
|
+
type BackupRunConfig = {
|
|
7
|
+
skipDryRun?: boolean;
|
|
8
|
+
skipPrune?: boolean;
|
|
9
|
+
ignoreErrors?: boolean;
|
|
10
|
+
};
|
|
5
11
|
|
|
6
12
|
// Get All Plans
|
|
7
13
|
export async function getAllPlans() {
|
|
@@ -137,14 +143,14 @@ export function useGetDownloadLogs() {
|
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
// Create New Plan
|
|
140
|
-
export async function createPlan(newPlan: NewPlanSettings) {
|
|
146
|
+
export async function createPlan({ newPlan, runSettings }: { newPlan: NewPlanSettings; runSettings: PlanAddRunSettings }) {
|
|
141
147
|
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
142
148
|
console.log('newPlan :', newPlan);
|
|
143
149
|
const res = await fetch(`${API_URL}/plans`, {
|
|
144
150
|
method: 'POST',
|
|
145
151
|
credentials: 'include',
|
|
146
152
|
headers: header,
|
|
147
|
-
body: JSON.stringify(newPlan),
|
|
153
|
+
body: JSON.stringify({ plan: newPlan, runSettings }),
|
|
148
154
|
});
|
|
149
155
|
const data = await res.json();
|
|
150
156
|
if (!data.success) {
|
|
@@ -227,12 +233,13 @@ export function useDeletePlan() {
|
|
|
227
233
|
}
|
|
228
234
|
|
|
229
235
|
// Perform Backup
|
|
230
|
-
export async function performBackup(id: string) {
|
|
236
|
+
export async function performBackup({ id, runConfig }: { id: string; runConfig?: BackupRunConfig }) {
|
|
231
237
|
const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
232
238
|
const res = await fetch(`${API_URL}/plans/${id}/action/backup`, {
|
|
233
239
|
method: 'POST',
|
|
234
240
|
credentials: 'include',
|
|
235
241
|
headers: header,
|
|
242
|
+
body: JSON.stringify({ runConfig }),
|
|
236
243
|
});
|
|
237
244
|
const data = await res.json();
|
|
238
245
|
if (!data.success) {
|
|
@@ -468,6 +475,35 @@ export function useCheckPlanIntegrity() {
|
|
|
468
475
|
});
|
|
469
476
|
}
|
|
470
477
|
|
|
478
|
+
export async function repairBackupPlan({ planId, type, replicationId }: { planId: string; type: string; replicationId?: string }) {
|
|
479
|
+
// const header = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
|
|
480
|
+
const res = await fetch(`${API_URL}/plans/${planId}/action/repair?type=${type}${replicationId ? `&replicationId=${replicationId}` : ''}`, {
|
|
481
|
+
method: 'POST',
|
|
482
|
+
credentials: 'include',
|
|
483
|
+
// headers: header,
|
|
484
|
+
});
|
|
485
|
+
// Check if response is ok
|
|
486
|
+
const data = await res.json();
|
|
487
|
+
if (!data.success) {
|
|
488
|
+
throw new Error(data.error);
|
|
489
|
+
}
|
|
490
|
+
return data;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function useRepairBackupPlan() {
|
|
494
|
+
const queryClient = useQueryClient();
|
|
495
|
+
return useMutation({
|
|
496
|
+
mutationFn: repairBackupPlan,
|
|
497
|
+
onSuccess: (res, payload) => {
|
|
498
|
+
queryClient.invalidateQueries({ queryKey: ['plan', payload.planId] });
|
|
499
|
+
console.log('res :', payload, res);
|
|
500
|
+
},
|
|
501
|
+
onError: (error, payload) => {
|
|
502
|
+
console.error('Error repairing backup plan for planId:', payload.planId, error);
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
471
507
|
export async function sendTestNotificationRequest(payload: {
|
|
472
508
|
planId: string;
|
|
473
509
|
notificationCase: string;
|
package/src/styles/global.scss
CHANGED
package/src/utils/helpers.ts
CHANGED
|
@@ -30,6 +30,8 @@ export const isMobile = (): boolean => {
|
|
|
30
30
|
return false;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
export const isDarkMode = window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
|
34
|
+
|
|
33
35
|
const formatter = new Intl.RelativeTimeFormat('en');
|
|
34
36
|
export const timeAgo = (input: Date) => {
|
|
35
37
|
const date = input instanceof Date ? input : new Date(input);
|
|
@@ -503,3 +505,26 @@ export const secondsToMinutes = (seconds: number) => {
|
|
|
503
505
|
}
|
|
504
506
|
return seconds > 60 ? `${Math.round(seconds / 60)} min` : `${seconds} sec`;
|
|
505
507
|
};
|
|
508
|
+
|
|
509
|
+
export const getIntervalMinutes = (interval: PlanInterval): number => {
|
|
510
|
+
switch (interval.type) {
|
|
511
|
+
case 'minutes':
|
|
512
|
+
return interval.minutes || 5;
|
|
513
|
+
case 'hours':
|
|
514
|
+
return parseInt(interval.hours || '1') * 60;
|
|
515
|
+
case 'hourly':
|
|
516
|
+
return 60;
|
|
517
|
+
case 'daily':
|
|
518
|
+
return 1440;
|
|
519
|
+
case 'days': {
|
|
520
|
+
const dayCount = interval.days?.split(',').filter(Boolean).length || 1;
|
|
521
|
+
return Math.round((7 / dayCount) * 1440);
|
|
522
|
+
}
|
|
523
|
+
case 'weekly':
|
|
524
|
+
return 10080;
|
|
525
|
+
case 'monthly':
|
|
526
|
+
return 43200;
|
|
527
|
+
default:
|
|
528
|
+
return 1440;
|
|
529
|
+
}
|
|
530
|
+
};
|