@plutonhq/core-frontend 0.1.26 → 0.1.27

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.
Files changed (70) hide show
  1. package/dist-lib/@types/backups.d.ts +3 -0
  2. package/dist-lib/@types/backups.d.ts.map +1 -1
  3. package/dist-lib/@types/plans.d.ts +2 -0
  4. package/dist-lib/@types/plans.d.ts.map +1 -1
  5. package/dist-lib/@types/restores.d.ts +2 -0
  6. package/dist-lib/@types/restores.d.ts.map +1 -1
  7. package/dist-lib/components/Device/DeviceInfo/DeviceInfo.module.scss.js +26 -26
  8. package/dist-lib/components/Plan/PlanBackups/PlanBackups.d.ts.map +1 -1
  9. package/dist-lib/components/Plan/PlanBackups/PlanBackups.js +26 -26
  10. package/dist-lib/components/Plan/PlanBackups/PlanBackups.js.map +1 -1
  11. package/dist-lib/components/Plan/PlanForm/PlanForm.d.ts.map +1 -1
  12. package/dist-lib/components/Plan/PlanForm/PlanForm.js +64 -80
  13. package/dist-lib/components/Plan/PlanForm/PlanForm.js.map +1 -1
  14. package/dist-lib/components/Plan/PlanHistory/PlanHistory.js +1 -1
  15. package/dist-lib/components/Plan/PlanHistory/PlanHistory.js.map +1 -1
  16. package/dist-lib/components/Plan/PlanSettings/PlanSourceSettings.d.ts.map +1 -1
  17. package/dist-lib/components/Plan/PlanSettings/PlanSourceSettings.js +39 -30
  18. package/dist-lib/components/Plan/PlanSettings/PlanSourceSettings.js.map +1 -1
  19. package/dist-lib/components/Plan/PlanStats/PlanStats.d.ts.map +1 -1
  20. package/dist-lib/components/Plan/PlanStats/PlanStats.js +25 -25
  21. package/dist-lib/components/Plan/PlanStats/PlanStats.js.map +1 -1
  22. package/dist-lib/components/Plan/PlanStats/PlanStats.module.scss.js +1 -1
  23. package/dist-lib/components/common/Icon/Icon.d.ts.map +1 -1
  24. package/dist-lib/components/common/Icon/Icon.js +12 -1
  25. package/dist-lib/components/common/Icon/Icon.js.map +1 -1
  26. package/dist-lib/components/common/form/IntervalField/IntervalField.d.ts.map +1 -1
  27. package/dist-lib/components/common/form/IntervalField/IntervalField.js +66 -46
  28. package/dist-lib/components/common/form/IntervalField/IntervalField.js.map +1 -1
  29. package/dist-lib/components/common/form/StoragePicker/StoragePicker.d.ts.map +1 -1
  30. package/dist-lib/components/common/form/StoragePicker/StoragePicker.js +49 -47
  31. package/dist-lib/components/common/form/StoragePicker/StoragePicker.js.map +1 -1
  32. package/dist-lib/components/common/form/TagsInput/TagsInput.d.ts +2 -1
  33. package/dist-lib/components/common/form/TagsInput/TagsInput.d.ts.map +1 -1
  34. package/dist-lib/components/common/form/TagsInput/TagsInput.js +15 -15
  35. package/dist-lib/components/common/form/TagsInput/TagsInput.js.map +1 -1
  36. package/dist-lib/components/common/form/TimePicker/TimePicker.module.scss.js +13 -13
  37. package/dist-lib/hooks/usePlanSingleActions.js +7 -7
  38. package/dist-lib/hooks/usePlanSingleActions.js.map +1 -1
  39. package/dist-lib/styles/core-frontend.css +1 -1
  40. package/dist-lib/styles/global.scss +6 -0
  41. package/dist-lib/utils/constants.js +1 -1
  42. package/dist-lib/utils/constants.js.map +1 -1
  43. package/dist-lib/utils/helpers.d.ts +2 -0
  44. package/dist-lib/utils/helpers.d.ts.map +1 -1
  45. package/dist-lib/utils/helpers.js +55 -33
  46. package/dist-lib/utils/helpers.js.map +1 -1
  47. package/dist-lib/utils/plans.js +6 -6
  48. package/dist-lib/utils/plans.js.map +1 -1
  49. package/dist-lib/utils.js +34 -33
  50. package/package.json +1 -1
  51. package/src/@types/backups.ts +3 -0
  52. package/src/@types/plans.ts +2 -0
  53. package/src/@types/restores.ts +2 -0
  54. package/src/components/Device/DeviceInfo/DeviceInfo.module.scss +1 -0
  55. package/src/components/Plan/PlanBackups/PlanBackups.tsx +5 -2
  56. package/src/components/Plan/PlanForm/PlanForm.tsx +1 -19
  57. package/src/components/Plan/PlanHistory/PlanHistory.tsx +1 -1
  58. package/src/components/Plan/PlanSettings/PlanSourceSettings.tsx +15 -1
  59. package/src/components/Plan/PlanStats/PlanStats.module.scss +3 -0
  60. package/src/components/Plan/PlanStats/PlanStats.tsx +2 -8
  61. package/src/components/common/Icon/Icon.tsx +12 -0
  62. package/src/components/common/form/IntervalField/IntervalField.tsx +21 -1
  63. package/src/components/common/form/StoragePicker/StoragePicker.tsx +8 -1
  64. package/src/components/common/form/TagsInput/TagsInput.tsx +3 -2
  65. package/src/components/common/form/TimePicker/TimePicker.module.scss +1 -0
  66. package/src/hooks/usePlanSingleActions.tsx +2 -2
  67. package/src/styles/global.scss +6 -0
  68. package/src/utils/constants.ts +1 -1
  69. package/src/utils/helpers.ts +25 -0
  70. package/src/utils/plans.ts +3 -3
@@ -5,7 +5,6 @@ import StoragePicker from '../../common/form/StoragePicker/StoragePicker';
5
5
  import PlanStrategySettings from '../PlanSettings/PlanStrategySettings';
6
6
  import PlanSourceSettings from '../PlanSettings/PlanSourceSettings';
7
7
  import { NewPlanSettings } from '../../../@types/plans';
8
- import NumberInput from '../../common/form/NumberInput/NumberInput';
9
8
  import classes from '../AddPlan/AddPlan.module.scss';
10
9
  import PFClasses from './PlanForm.module.scss';
11
10
  import { useGetSettings } from '../../../services/settings';
@@ -254,30 +253,13 @@ const PlanForm = ({
254
253
  <div className={PFClasses.planStep}>
255
254
  <div className={classes.field} style={{ width: '150px' }}>
256
255
  <IntervalField
257
- label="Backup Interval*"
256
+ label="Backup Interval"
258
257
  fieldValue={planSettings.settings.interval}
259
258
  onUpdate={(intervalSettings) =>
260
259
  onPlanSettingsChange({ ...planSettings, settings: { ...planSettings.settings, interval: intervalSettings } })
261
260
  }
262
261
  />
263
262
  </div>
264
-
265
- <div className={classes.field} style={{ width: '150px' }}>
266
- <NumberInput
267
- label="Backups to Keep"
268
- fieldValue={planSettings.settings.prune.snapCount}
269
- onUpdate={(val) =>
270
- onPlanSettingsChange({
271
- ...planSettings,
272
- settings: { ...planSettings.settings, prune: { ...planSettings.settings.prune, snapCount: val } },
273
- })
274
- }
275
- placeholder="5"
276
- min={1}
277
- inline={false}
278
- hint="Number of Active Restorable Backups/Snapshots to Keep regardless of the Removal Policy"
279
- />
280
- </div>
281
263
  <PlanPruneSettings
282
264
  plan={planSettings}
283
265
  onUpdate={(pruneSettings) =>
@@ -52,7 +52,7 @@ const PlanHistory = ({ planId, history, itemsCount = 10 }: PlanHistoryProps) =>
52
52
  <div><b>Changes</b>: ${backupChanges(h.changes)}</div>
53
53
  <div><b>Started</b>: ${formatDateTime(h.started)}</div>
54
54
  <div><b>Ended</b>: ${h.ended ? formatDateTime(h.ended) : ' - '}</div>
55
- <div><b>Duration</b>: ${duration ? formatDuration(duration) : '0ms'} </div>
55
+ <div><b>Duration</b>: ${h.status !== 'started' && duration ? formatDuration(duration) : '-'} </div>
56
56
  `}
57
57
  data-tooltip-place="top"
58
58
  ></div>
@@ -1,3 +1,4 @@
1
+ import { useEffect } from 'react';
1
2
  import classes from './PlanSettings.module.scss';
2
3
  import PathPicker from '../../common/PathPicker/PathPicker';
3
4
  import { NewPlanSettings } from '../../../@types/plans';
@@ -27,6 +28,19 @@ const PlanSourceSettings = ({ plan, onUpdate, error, isEditing }: PlanSourceSett
27
28
  );
28
29
  }
29
30
 
31
+ // When the device changes, reset the sourceConfig paths to prevent invalid paths from being submitted
32
+ useEffect(() => {
33
+ if (!isEditing) {
34
+ onUpdate({
35
+ ...plan,
36
+ sourceConfig: {
37
+ includes: [],
38
+ excludes: [],
39
+ },
40
+ });
41
+ }
42
+ }, [isEditing, deviceId]);
43
+
30
44
  return (
31
45
  <>
32
46
  <div className={classes.field}>
@@ -47,7 +61,7 @@ const PlanSourceSettings = ({ plan, onUpdate, error, isEditing }: PlanSourceSett
47
61
  onUpdate={(paths) => onUpdate({ ...plan, sourceConfig: { ...paths } })}
48
62
  deviceId={deviceId}
49
63
  single={plan.method === 'sync'}
50
- disallowChange={plan.method === 'sync'}
64
+ disallowChange={plan.method === 'sync' && isEditing}
51
65
  />
52
66
  </div>
53
67
  </>
@@ -1,5 +1,6 @@
1
1
  .planStats {
2
2
  display: flex;
3
+ justify-content: space-between;
3
4
  gap: 20px;
4
5
  .widgetTitle {
5
6
  padding: 2px 6px;
@@ -42,6 +43,7 @@
42
43
  gap: 5px;
43
44
  font-size: 0.75rem;
44
45
  font-weight: 500;
46
+ min-width: 80px;
45
47
  img {
46
48
  width: auto;
47
49
  max-height: 18px;
@@ -55,6 +57,7 @@
55
57
  border: 1px solid var(--line-color);
56
58
  border-radius: 50%;
57
59
  width: 30px;
60
+ min-width: 30px;
58
61
  height: 30px;
59
62
  box-sizing: border-box;
60
63
  padding-top: 4px;
@@ -1,6 +1,6 @@
1
1
  import Icon from '../../common/Icon/Icon';
2
2
  import { Plan } from '../../../@types/plans';
3
- import { formatBytes, formatNumberToK } from '../../../utils/helpers';
3
+ import { formatBytes, formatNumberToK, formatIntervalDisplay } from '../../../utils/helpers';
4
4
  import PlanHistory from '../PlanHistory/PlanHistory';
5
5
  import classes from './PlanStats.module.scss';
6
6
  import PlanStorageInfo from '../PlanStorageInfo/PlanStorageInfo';
@@ -45,13 +45,7 @@ const PlanStats = ({ plan, isSync, lastBackupItem }: PlanStatsProps) => {
45
45
  <div
46
46
  data-tooltip-id="htmlToolTip"
47
47
  data-tooltip-place="top"
48
- data-tooltip-html={
49
- isActive
50
- ? isSync
51
- ? `Syncing changes every ${interval.minutes} minutes`
52
- : `Copying changes ${['hours', 'minutes', 'days'].includes(interval.type) ? interval[interval.type as 'hours' | 'minutes' | 'days'] + interval.type : interval.type}`
53
- : 'Plan is Not Active'
54
- }
48
+ data-tooltip-html={isActive ? `${isSync ? 'Syncing' : 'Copying'} changes ${formatIntervalDisplay(interval)}` : 'Plan is Not Active'}
55
49
  >
56
50
  <Icon type={isActive ? (isSync ? 'reload' : 'copy') : 'pause'} size={16} />
57
51
  <div className={classes.sourceArrow}>→</div>
@@ -732,6 +732,18 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
732
732
  </g>
733
733
  </IconWrapper>
734
734
  )}
735
+ {type === 'folder-exclude' && (
736
+ <IconWrapper size={size} viewBox="0 0 24 24">
737
+ <path
738
+ fill="none"
739
+ stroke={color}
740
+ strokeLinecap="round"
741
+ strokeLinejoin="round"
742
+ strokeWidth={2}
743
+ d="M13.5 19H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4l3 3h7a2 2 0 0 1 2 2v4m1 9l-5-5m0 5l5-5"
744
+ ></path>
745
+ </IconWrapper>
746
+ )}
735
747
 
736
748
  {type === 'folders' && (
737
749
  <IconWrapper size={size} viewBox="0 0 24 24">
@@ -1,3 +1,4 @@
1
+ import { NumberInput } from '../../..';
1
2
  import { PlanInterval } from '../../../../@types/plans';
2
3
  import Icon from '../../Icon/Icon';
3
4
  import FormField from '../FormField/FormField';
@@ -44,6 +45,7 @@ const IntervalField = ({
44
45
  { label: 'Daily', value: 'daily' },
45
46
  { label: 'Weekly', value: 'weekly' },
46
47
  { label: 'Monthly', value: 'monthly' },
48
+ { label: 'Every x Minutes', value: 'minutes' },
47
49
  { label: 'Every x Hours', value: 'hours' },
48
50
  { label: 'Every x Days', value: 'days' },
49
51
  ]}
@@ -84,7 +86,7 @@ const IntervalField = ({
84
86
  </div>
85
87
  </>
86
88
  )}
87
- {fieldValue.type !== 'hourly' && fieldValue.type !== 'hours' && (
89
+ {fieldValue.type !== 'hourly' && fieldValue.type !== 'hours' && fieldValue.type !== 'minutes' && (
88
90
  <>
89
91
  <span>at</span>
90
92
  <TimePicker fieldValue={fieldValue.time || '10:00AM'} onUpdate={(val) => onUpdate({ ...fieldValue, time: val })} />
@@ -123,6 +125,24 @@ const IntervalField = ({
123
125
  />
124
126
  </>
125
127
  )}
128
+ {fieldValue.type === 'minutes' && (
129
+ <>
130
+ <span>Every</span>
131
+ <NumberInput
132
+ min={5}
133
+ inline={true}
134
+ fieldValue={fieldValue.minutes || 5}
135
+ onUpdate={(minutes) =>
136
+ onUpdate({
137
+ ...fieldValue,
138
+ minutes: minutes,
139
+ })
140
+ }
141
+ placeholder="1"
142
+ />{' '}
143
+ Minutes
144
+ </>
145
+ )}
126
146
  {fieldValue.type === 'hours' && (
127
147
  <>
128
148
  <span>Every</span>
@@ -70,6 +70,13 @@ const StoragePicker = ({ onUpdate, storagePath = '', storageId, disabled = false
70
70
  }
71
71
  }, [selectedStorage, path]);
72
72
 
73
+ useEffect(() => {
74
+ if (!disabled) {
75
+ setSelectedStorage(null);
76
+ setPath('');
77
+ }
78
+ }, [deviceId, disabled]);
79
+
73
80
  // console.log('Storage path :', path, !disabled && isLocalStorage && !path);
74
81
 
75
82
  return (
@@ -98,7 +105,7 @@ const StoragePicker = ({ onUpdate, storagePath = '', storageId, disabled = false
98
105
  <Input
99
106
  disabled={disabled}
100
107
  fieldValue={path}
101
- onUpdate={(val) => setPath(val.startsWith('/') ? val.slice(1) : val)} //if the val starts with a slash remove it
108
+ onUpdate={(val) => setPath(!isLocalStorage && val.startsWith('/') ? val.slice(1) : val)} //if the val starts with a slash remove it (only for remote storages, local paths need the leading slash)
102
109
  placeholder={isLocalStorage ? 'Select a folder' : hasBucketName ? 'subfolder' : `folder-or-bucket/subfolder`}
103
110
  full={true}
104
111
  required={!disabled && isLocalStorage}
@@ -10,11 +10,12 @@ type TagsInputProps = {
10
10
  icon?: string;
11
11
  type?: string;
12
12
  inline?: boolean;
13
+ hint?: string;
13
14
  fieldValue: string[];
14
15
  onUpdate: (f: string[]) => void;
15
16
  };
16
17
 
17
- const TagsInput = ({ label, description, customClasses = '', fieldValue = [], onUpdate, icon, type = 'tag', inline }: TagsInputProps) => {
18
+ const TagsInput = ({ label, description, customClasses = '', hint = '', fieldValue = [], onUpdate, icon, type = 'tag', inline }: TagsInputProps) => {
18
19
  const [tags, setTags] = useState<string[]>(() => (Array.isArray(fieldValue) ? fieldValue : []));
19
20
  const [newTag, setNewTag] = useState('');
20
21
 
@@ -56,7 +57,7 @@ const TagsInput = ({ label, description, customClasses = '', fieldValue = [], on
56
57
  };
57
58
 
58
59
  return (
59
- <FormField type={'tags'} label={label} description={description} inline={inline} classes={`${classes.tagField} ${customClasses}`}>
60
+ <FormField type={'tags'} label={label} hint={hint} description={description} inline={inline} classes={`${classes.tagField} ${customClasses}`}>
60
61
  <div className={classes.tagBox}>
61
62
  {tags.map((t, i) => (
62
63
  <div className={classes.tag} key={t + i}>
@@ -28,6 +28,7 @@
28
28
 
29
29
  .timeSelect {
30
30
  position: absolute;
31
+ z-index: 33;
31
32
  background-color: var(--field-bg);
32
33
  border: 1px solid var(--field-line-color);
33
34
  padding: 10px;
@@ -50,8 +50,8 @@ export const usePlanSingleActions = (): {
50
50
 
51
51
  const sortedHistory = [...(plan.backups || [])].sort((a, b) => b.started - a.started);
52
52
  const activeBackups = sortedHistory.filter((s) => s.inProgress);
53
- const activeRestores = (plan.restores || []).filter((s) => s.status === 'started');
54
- const lastBackupItem = sortedHistory[0];
53
+ const activeRestores = (plan.restores || []).filter((s) => s.inProgress);
54
+ const lastBackupItem = isSync ? sortedHistory.filter((s) => s.status === 'completed')[0] : sortedHistory[0];
55
55
  const actionInProgress = activeBackups.length > 0 || activeRestores.length > 0;
56
56
  const taskPending = pauseMutation.isPending || resumeMutation.isPending || performBackupMutation.isPending;
57
57
 
@@ -359,3 +359,9 @@ i.pipe {
359
359
  transform: translateX(100%);
360
360
  }
361
361
  }
362
+
363
+ @media (max-width: 768px) {
364
+ :root {
365
+ --container-width: 100%;
366
+ }
367
+ }
@@ -57,7 +57,7 @@ export const DEFAULT_PLAN_SETTINGS: NewPlanSettings = {
57
57
  snapCount: 5,
58
58
  policy: 'forgetByAge',
59
59
  forgetAge: '1m',
60
- revisions: true,
60
+ revisions: false,
61
61
  },
62
62
  performance: {
63
63
  scan: true,
@@ -1,5 +1,6 @@
1
1
  import { storageOptionField } from '../@types/storages';
2
2
  import { FileItem } from '../@types/system';
3
+ import { PlanInterval } from '../@types/plans';
3
4
 
4
5
  export const isMobile = (): boolean => {
5
6
  // Server-side rendering check
@@ -472,6 +473,30 @@ export const getAvailableCliApps = (platform?: string) => {
472
473
  }
473
474
  };
474
475
 
476
+ export const formatIntervalDisplay = (interval: PlanInterval): string => {
477
+ const time = interval.time ? ` at ${interval.time}` : '';
478
+ switch (interval.type) {
479
+ case 'hourly':
480
+ return 'every hour';
481
+ case 'hours':
482
+ return `every ${interval.hours?.replace('hrs', '')} hours`;
483
+ case 'minutes':
484
+ return `every ${interval.minutes} minutes`;
485
+ case 'daily':
486
+ return `daily${time}`;
487
+ case 'weekly':
488
+ return `weekly on ${interval.days}${time}`;
489
+ case 'days': {
490
+ const dayList = interval.days?.replace(/-$/, '').replace(/-/g, ', ');
491
+ return `every ${dayList}${time}`;
492
+ }
493
+ case 'monthly':
494
+ return `monthly (${interval.days})${time}`;
495
+ default:
496
+ return interval.type;
497
+ }
498
+ };
499
+
475
500
  export const secondsToMinutes = (seconds: number) => {
476
501
  if (seconds === 0) {
477
502
  return '';
@@ -13,11 +13,11 @@ export function planIntervalName(interval: PlanInterval): string {
13
13
  case 'monthly':
14
14
  return 'Every Month';
15
15
  case 'days':
16
- return `Every ${interval.days} days`;
16
+ return `Every ${interval.days}`;
17
17
  case 'hours':
18
- return `Every ${interval.hours} hrs`;
18
+ return `Every ${interval.hours}`;
19
19
  case 'minutes':
20
- return `Every ${interval.minutes} mins`;
20
+ return `Every ${interval.minutes}`;
21
21
  default:
22
22
  return '';
23
23
  }