@plutonhq/core-frontend 0.1.25 → 0.1.26

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 (26) hide show
  1. package/dist-lib/@types/backups.d.ts +1 -1
  2. package/dist-lib/@types/backups.d.ts.map +1 -1
  3. package/dist-lib/components/Plan/BackupEvents/BackupEvents.d.ts.map +1 -1
  4. package/dist-lib/components/Plan/BackupEvents/BackupEvents.js +27 -27
  5. package/dist-lib/components/Plan/BackupEvents/BackupEvents.js.map +1 -1
  6. package/dist-lib/components/Plan/PlanPruneModal/PlanPruneModal.d.ts.map +1 -1
  7. package/dist-lib/components/Plan/PlanPruneModal/PlanPruneModal.js +62 -26
  8. package/dist-lib/components/Plan/PlanPruneModal/PlanPruneModal.js.map +1 -1
  9. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.d.ts.map +1 -1
  10. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js +138 -62
  11. package/dist-lib/components/Plan/PlanSettings/PlanPruneSettings.js.map +1 -1
  12. package/dist-lib/components/Plan/PlanSettings/PlanSettings.module.scss.js +42 -42
  13. package/dist-lib/components/common/StatusLabel/StatusLabel.d.ts +1 -1
  14. package/dist-lib/components/common/StatusLabel/StatusLabel.d.ts.map +1 -1
  15. package/dist-lib/components/common/StatusLabel/StatusLabel.js +17 -12
  16. package/dist-lib/components/common/StatusLabel/StatusLabel.js.map +1 -1
  17. package/dist-lib/components/common/form/NumberInput/NumberInput.module.scss.js +4 -4
  18. package/dist-lib/styles/core-frontend.css +1 -1
  19. package/package.json +1 -1
  20. package/src/@types/backups.ts +1 -1
  21. package/src/components/Plan/BackupEvents/BackupEvents.tsx +5 -3
  22. package/src/components/Plan/PlanPruneModal/PlanPruneModal.tsx +54 -11
  23. package/src/components/Plan/PlanSettings/PlanPruneSettings.tsx +145 -61
  24. package/src/components/Plan/PlanSettings/PlanSettings.module.scss +5 -0
  25. package/src/components/common/StatusLabel/StatusLabel.tsx +7 -1
  26. package/src/components/common/form/NumberInput/NumberInput.module.scss +1 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutonhq/core-frontend",
3
3
  "description": "Pluton Core Frontend Library",
4
- "version": "0.1.25",
4
+ "version": "0.1.26",
5
5
  "author": "Plutonhq",
6
6
  "license": "Apache-2.0",
7
7
  "publishConfig": {
@@ -55,7 +55,7 @@ export type Backup = {
55
55
  started: number;
56
56
  ended: number;
57
57
  duration: number;
58
- status: 'completed' | 'cancelled' | 'failed' | 'started';
58
+ status: 'completed' | 'cancelled' | 'failed' | 'started' | 'retrying' | 'initializing';
59
59
  inProgress: boolean;
60
60
  totalFiles: number;
61
61
  totalSize: number;
@@ -57,9 +57,11 @@ const BackupEvents = ({ id, type = 'backup', sourceId, sourceType, planId, inPro
57
57
  <Icon type={inProgress ? 'loading' : hasFinished === 'Completed' ? 'check-circle-filled' : 'error-circle-filled'} size={14} />{' '}
58
58
  {inProgress ? `${type} In Progress` : `${type} ${hasFinished}` || 'Unknown'}
59
59
  </div>
60
- <div title="Duration" className={classes.duration}>
61
- <Icon type="clock" size={14} /> {(progressDataToUse?.duration && formatDuration(progressDataToUse.duration / 1000)) || 'N/A'}
62
- </div>
60
+ {!inProgress && hasFinished && (
61
+ <div title="Duration" className={classes.duration}>
62
+ <Icon type="clock" size={14} /> {(progressDataToUse?.duration && formatDuration(progressDataToUse.duration / 1000)) || 'N/A'}
63
+ </div>
64
+ )}
63
65
  </div>
64
66
  {isLoading && (
65
67
  <div className={classes.loading}>
@@ -15,6 +15,56 @@ interface PlanPruneModalProps {
15
15
  const PlanPruneModal = ({ planId, method, prune, snapshotsCount, taskPending, close }: PlanPruneModalProps) => {
16
16
  const pruneMutation = usePrunePlan();
17
17
 
18
+ const getBackupMessage = () => {
19
+ switch (prune.policy) {
20
+ case 'keepLast':
21
+ return (
22
+ <>
23
+ Your plan is set to keep the last {prune.snapCount} backups.{' '}
24
+ {prune.snapCount < snapshotsCount
25
+ ? `Running prune now will remove ${snapshotsCount - prune.snapCount} older snapshots, freeing up some storage space.`
26
+ : 'There are no excess snapshots to clean up.'}
27
+ </>
28
+ );
29
+ case 'forgetByAge':
30
+ return (
31
+ <>
32
+ Your plan is set to remove backups older than {planIntervalAgeName(prune.forgetAge || '3m')}, while always keeping at least{' '}
33
+ {prune.snapCount || 1} backup{(prune.snapCount || 1) > 1 ? 's' : ''}. This action will remove any backups that exceed the retention
34
+ policy.
35
+ </>
36
+ );
37
+ case 'custom':
38
+ return (
39
+ <>
40
+ Your plan uses an advanced retention policy
41
+ {prune.keepDailySnaps ? `, keeping daily backups for ${prune.keepDailySnaps} days` : ''}
42
+ {prune.keepWeeklySnaps ? `, weekly backups for ${prune.keepWeeklySnaps} weeks` : ''}
43
+ {prune.keepMonthlySnaps ? `, monthly backups for ${prune.keepMonthlySnaps} months` : ''}, while always keeping at least{' '}
44
+ {prune.snapCount || 1} backup{(prune.snapCount || 1) > 1 ? 's' : ''}. This action will remove any backups that exceed the retention
45
+ policy.
46
+ </>
47
+ );
48
+ case 'disable':
49
+ return <>Pruning is disabled for this plan. No backups will be removed.</>;
50
+ default:
51
+ return (
52
+ <>
53
+ Your plan is set to maintain {prune.snapCount} recent backups.{' '}
54
+ {prune.snapCount < snapshotsCount
55
+ ? `Running prune now will remove ${snapshotsCount - prune.snapCount} older snapshots, freeing up some storage space.`
56
+ : 'There are no excess snapshots to clean up.'}
57
+ </>
58
+ );
59
+ }
60
+ };
61
+
62
+ const canPrune = () => {
63
+ if (prune.policy === 'disable') return false;
64
+ if (prune.policy === 'keepLast' && prune.snapCount >= snapshotsCount) return false;
65
+ return true;
66
+ };
67
+
18
68
  return (
19
69
  <ActionModal
20
70
  title={method === 'sync' ? 'Clean Up Old Revisions' : 'Clean Up Old Backups'}
@@ -23,25 +73,18 @@ const PlanPruneModal = ({ planId, method, prune, snapshotsCount, taskPending, cl
23
73
  {method === 'sync' ? (
24
74
  <>
25
75
  Your plan is set to maintain{' '}
26
- {prune.policy === 'forgetByAge' ? `file revisions newer than ${planIntervalAgeName(prune.forgetAge || '3m')}.` : ''}{' '}
27
- {prune.policy === 'forgetByFileCount' ? `Only keep the last ${prune.snapCount} versions of each file.` : ''} This action will
28
- remove any excess file revisions older than the retention policy.
76
+ {prune.policy === 'forgetByAge' ? `file revisions newer than ${planIntervalAgeName(prune.forgetAge || '3m')}.` : ''} This action
77
+ will remove any excess file revisions older than the retention policy.
29
78
  </>
30
79
  ) : (
31
- <>
32
- Your plan is set to maintain {prune.snapCount} recent backups(snapshots).{' '}
33
- {prune.snapCount < snapshotsCount
34
- ? `Running prune now will remove ${' '}
35
- ${snapshotsCount - prune.snapCount} older snapshots, freeing up some storage space`
36
- : 'There are no excess snapshots to clean up.'}
37
- </>
80
+ getBackupMessage()
38
81
  )}
39
82
  </>
40
83
  }
41
84
  closeModal={() => !pruneMutation.isPending && close()}
42
85
  width="500px"
43
86
  primaryAction={{
44
- title: method === 'sync' ? `Yes, Remove Old Revisions` : prune.snapCount < snapshotsCount ? 'Yes, Remove Old Backups' : '',
87
+ title: method === 'sync' ? 'Yes, Remove Old Revisions' : canPrune() ? 'Yes, Remove Old Backups' : '',
45
88
  type: 'default',
46
89
  isPending: pruneMutation.isPending,
47
90
  action: () =>
@@ -1,9 +1,9 @@
1
1
  import classes from './PlanSettings.module.scss';
2
2
  import Select from '../../common/form/Select/Select';
3
- import Input from '../../common/form/Input/Input';
4
3
  import Tristate from '../../common/form/Tristate/Tristate';
5
4
  import { NewPlanSettings } from '../../../@types/plans';
6
5
  import NumberInput from '../../common/form/NumberInput/NumberInput';
6
+ import Toggle from '../../common/form/Toggle/Toggle';
7
7
 
8
8
  interface PlanPruneSettingsProps {
9
9
  plan: NewPlanSettings;
@@ -11,7 +11,7 @@ interface PlanPruneSettingsProps {
11
11
  }
12
12
 
13
13
  const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
14
- const pruneSettings = plan.settings?.prune;
14
+ const pruneSettings = plan.settings?.prune || {};
15
15
 
16
16
  return (
17
17
  <>
@@ -19,83 +19,167 @@ const PlanPruneSettings = ({ plan, onUpdate }: PlanPruneSettingsProps) => {
19
19
  <label className={classes.label}>Backup Retention Policy</label>
20
20
  <Select
21
21
  options={[
22
- { label: 'Remove by Age', value: 'forgetByAge' },
23
- { label: 'Remove by Date', value: 'forgetByDate' },
24
- { label: 'Custom Prune Policy', value: 'custom' },
22
+ { label: 'Keep Number of Backups', value: 'keepLast' },
23
+ { label: 'Remove Backups By Age', value: 'forgetByAge' },
24
+ { label: 'Advanced Policy', value: 'custom' },
25
+ { label: 'Keep All Backups (Disable Pruning)', value: 'disable' },
25
26
  ]}
26
- fieldValue={pruneSettings.policy}
27
- onUpdate={(val: string) => onUpdate({ ...pruneSettings, policy: val })}
27
+ fieldValue={pruneSettings.policy || 'keepLast'}
28
+ onUpdate={(val: string) => {
29
+ // Reset snapCount to a default safe value if switching contexts, or retain if preferred
30
+ onUpdate({ ...pruneSettings, policy: val, snapCount: val === 'keepLast' ? 5 : pruneSettings.snapCount });
31
+ }}
28
32
  />
29
33
  </div>
30
- {pruneSettings.policy === 'forgetByAge' && (
34
+
35
+ {/* OPTION 1: KEEP LAST N */}
36
+ {pruneSettings.policy === 'keepLast' && (
31
37
  <div className={classes.field}>
32
- <label className={classes.label}>Remove Backups Older Than</label>
33
- <div className={classes.forgetByAgeField}>
34
- <NumberInput
35
- fieldValue={pruneSettings.forgetAge ? parseInt(pruneSettings.forgetAge.replace(/\D/g, ''), 10) : 3}
36
- onUpdate={(val) =>
37
- onUpdate({
38
- ...pruneSettings,
39
- forgetAge: (pruneSettings.forgetAge || '3m').replace(/\d+/g, val.toString()),
40
- })
41
- }
42
- placeholder="5"
43
- min={1}
44
- full={true}
45
- />
46
- <Tristate
47
- fieldValue={(pruneSettings.forgetAge || '3m').replace(/\d/g, '')}
48
- options={[
49
- { label: 'Days', value: 'd' },
50
- { label: 'Weeks', value: 'w' },
51
- { label: 'Months', value: 'm' },
52
- ]}
53
- onUpdate={(val: string) =>
54
- onUpdate({
55
- ...pruneSettings,
56
- forgetAge: (pruneSettings.forgetAge || '3m').replace(/\D/g, val),
57
- })
58
- }
59
- />
60
- </div>
61
- </div>
62
- )}
63
- {pruneSettings.policy === 'forgetByDate' && (
64
- <div className={classes.field} style={{ width: '200px' }}>
65
- <label className={classes.label}>Remove Backups Older Than</label>
66
- <Input type="date" fieldValue={pruneSettings.forgetDate || ''} onUpdate={(val) => onUpdate({ ...pruneSettings, forgetDate: val })} />
38
+ <NumberInput
39
+ label="Number of Backups to Keep"
40
+ fieldValue={pruneSettings.snapCount || 5}
41
+ onUpdate={(val) => onUpdate({ ...pruneSettings, snapCount: val })}
42
+ placeholder="5"
43
+ min={1}
44
+ hint="Only the most recent 5 backups will be kept."
45
+ inline={false}
46
+ />
67
47
  </div>
68
48
  )}
69
- {pruneSettings.policy === 'custom' && (
49
+
50
+ {/* OPTION 2: REMOVE BY AGE */}
51
+ {pruneSettings.policy === 'forgetByAge' && (
70
52
  <>
71
53
  <div className={classes.field}>
72
- <label className={classes.label}>Custom Policy Settings</label>
73
- <div className={classes.customPolicyOption}>
74
- <span>Keep Daily Backups for </span>
54
+ <label className={classes.label}>Remove Backups Older Than</label>
55
+ <div className={classes.forgetByAgeField}>
75
56
  <NumberInput
76
- fieldValue={pruneSettings.keepDailySnaps || ''}
77
- onUpdate={(val) => onUpdate({ ...pruneSettings, keepDailySnaps: val })}
57
+ fieldValue={pruneSettings.forgetAge ? parseInt(pruneSettings.forgetAge.replace(/\D/g, ''), 10) : 3}
58
+ onUpdate={(val) =>
59
+ onUpdate({
60
+ ...pruneSettings,
61
+ forgetAge: (pruneSettings.forgetAge || '3m').replace(/\d+/g, val.toString()),
62
+ })
63
+ }
64
+ placeholder="3"
65
+ min={1}
66
+ full={true}
67
+ />
68
+ <Tristate
69
+ fieldValue={(pruneSettings.forgetAge || '3m').replace(/\d/g, '')}
70
+ options={[
71
+ { label: 'Days', value: 'd' },
72
+ { label: 'Weeks', value: 'w' },
73
+ { label: 'Months', value: 'm' },
74
+ { label: 'Years', value: 'y' },
75
+ ]}
76
+ onUpdate={(val: string) =>
77
+ onUpdate({
78
+ ...pruneSettings,
79
+ forgetAge: (pruneSettings.forgetAge || '3m').replace(/\D/g, val),
80
+ })
81
+ }
78
82
  />
79
- <span>Days</span>
80
83
  </div>
81
- <div className={classes.customPolicyOption}>
82
- <span>Keep Weekly Backups for </span>
84
+ </div>
85
+
86
+ {/* Fail-safe keeping minimum N backups so they aren't left with 0 backups if backups fail for a month */}
87
+ <div className={classes.field}>
88
+ <label className={classes.label}>Minimum numbers of backups to keep</label>
89
+ <div>
83
90
  <NumberInput
84
- fieldValue={pruneSettings.keepWeeklySnaps || ''}
85
- onUpdate={(val) => onUpdate({ ...pruneSettings, keepWeeklySnaps: val })}
91
+ fieldValue={pruneSettings.snapCount || 1}
92
+ onUpdate={(val) => onUpdate({ ...pruneSettings, snapCount: val })}
93
+ min={1}
94
+ placeholder="3"
95
+ full={true}
86
96
  />
87
- <span>Weeks</span>
88
97
  </div>
89
- <div className={classes.customPolicyOption}>
90
- <span>Keep Monthly Backups for </span>
98
+ </div>
99
+ </>
100
+ )}
101
+
102
+ {/* OPTION 3: ADVANCED */}
103
+ {pruneSettings.policy === 'custom' && (
104
+ <div className={classes.field}>
105
+ <label className={classes.label}>Advanced Policy Settings</label>
106
+ <small className={classes.helperText} style={{ marginBottom: '15px', display: 'block', color: 'var(--text-muted)' }}>
107
+ A backup is kept if it matches <strong>ANY</strong> of the checked rules below.
108
+ </small>
109
+
110
+ {/* Keep Daily */}
111
+ <div className={classes.customPolicyOption}>
112
+ <Toggle
113
+ fieldValue={!!pruneSettings.keepDailySnaps}
114
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepDailySnaps: val ? 7 : undefined })}
115
+ customClasses={classes.removeRemoteToggle}
116
+ inline={true}
117
+ />
118
+ <span>Keep the latest daily backup for</span>
119
+ <NumberInput
120
+ fieldValue={pruneSettings.keepDailySnaps || ''}
121
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepDailySnaps: val })}
122
+ min={1}
123
+ />
124
+ <span>Days</span>
125
+ </div>
126
+
127
+ {/* Keep Weekly */}
128
+ <div className={classes.customPolicyOption}>
129
+ <Toggle
130
+ fieldValue={!!pruneSettings.keepWeeklySnaps}
131
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepWeeklySnaps: val ? 4 : undefined })}
132
+ customClasses={classes.removeRemoteToggle}
133
+ inline={true}
134
+ />
135
+ <span>Keep the latest weekly backup for</span>
136
+ <NumberInput
137
+ fieldValue={pruneSettings.keepWeeklySnaps || ''}
138
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepWeeklySnaps: val })}
139
+ min={1}
140
+ />
141
+ <span>Weeks</span>
142
+ </div>
143
+
144
+ {/* Keep Monthly */}
145
+ <div className={classes.customPolicyOption}>
146
+ <Toggle
147
+ fieldValue={!!pruneSettings.keepMonthlySnaps}
148
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepMonthlySnaps: val ? 12 : undefined })}
149
+ customClasses={classes.removeRemoteToggle}
150
+ inline={true}
151
+ />
152
+ <span>Keep the latest monthly backup for</span>
153
+ <NumberInput
154
+ fieldValue={pruneSettings.keepMonthlySnaps || ''}
155
+ onUpdate={(val) => onUpdate({ ...pruneSettings, keepMonthlySnaps: val })}
156
+ min={1}
157
+ />
158
+ <span>Months</span>
159
+ </div>
160
+ {/* Fail-safe keeping minimum N backups so they aren't left with 0 backups if backups fail for a month */}
161
+ <div className={classes.field} style={{ marginTop: '15px' }}>
162
+ <label className={classes.label}>Minimum numbers of backups to keep</label>
163
+ <div>
91
164
  <NumberInput
92
- fieldValue={pruneSettings.keepMonthlySnaps || ''}
93
- onUpdate={(val) => onUpdate({ ...pruneSettings, keepMonthlySnaps: val })}
165
+ fieldValue={pruneSettings.snapCount || 1}
166
+ onUpdate={(val) => onUpdate({ ...pruneSettings, snapCount: val })}
167
+ min={1}
168
+ placeholder="3"
169
+ full={true}
94
170
  />
95
- <span>Months</span>
96
171
  </div>
97
172
  </div>
98
- </>
173
+ </div>
174
+ )}
175
+
176
+ {/* OPTION 4: DISABLE */}
177
+ {pruneSettings.policy === 'disable' && (
178
+ <div className={classes.field}>
179
+ <small className={classes.helperText}>
180
+ Pruning is disabled. Old backups will never be deleted. Make sure you have enough storage!
181
+ </small>
182
+ </div>
99
183
  )}
100
184
  </>
101
185
  );
@@ -113,10 +113,15 @@
113
113
  flex-direction: row;
114
114
  align-items: center;
115
115
  margin-bottom: 10px;
116
+ gap: 10px;
116
117
  & > div {
117
118
  width: 100px;
118
119
  margin: 0 10px;
119
120
  }
121
+ div[class*='_toggleField'] {
122
+ width: auto;
123
+ margin: 0;
124
+ }
120
125
  }
121
126
 
122
127
  .typeFieldActive {
@@ -1,7 +1,7 @@
1
1
  import Icon from '../Icon/Icon';
2
2
 
3
3
  interface StatusLabelProps {
4
- status: 'completed' | 'cancelled' | 'failed' | 'started';
4
+ status: 'completed' | 'cancelled' | 'failed' | 'started' | 'retrying' | 'initializing';
5
5
  hasError: boolean;
6
6
  }
7
7
 
@@ -30,6 +30,12 @@ const StatusLabel = ({ status, hasError }: StatusLabelProps) => {
30
30
  <Icon type={'loading'} size={15} /> In Progress
31
31
  </div>
32
32
  );
33
+ } else if (status === 'initializing') {
34
+ return (
35
+ <div>
36
+ <Icon type={'loading'} size={15} /> Initializing
37
+ </div>
38
+ );
33
39
  }
34
40
  };
35
41
 
@@ -92,6 +92,7 @@
92
92
  width: 100%;
93
93
  .input {
94
94
  width: 100%;
95
+ min-width: 120px;
95
96
  }
96
97
  }
97
98
  &.fieldHasError {