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