diff --git a/internal/config/validate.go b/internal/config/validate.go index 8aeb4593..eeb1432b 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -10,6 +10,7 @@ import ( "github.com/garethgeorge/backrest/internal/config/validationutil" "github.com/gitploy-io/cronexpr" "github.com/hashicorp/go-multierror" + "google.golang.org/protobuf/proto" ) func ValidateConfig(c *v1.Config) error { @@ -107,6 +108,10 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error { if plan.Retention != nil && plan.Retention.Policy == nil { err = multierror.Append(err, errors.New("retention policy must be nil or must specify a policy")) + } else if policyTimeBucketed, ok := plan.Retention.Policy.(*v1.RetentionPolicy_PolicyTimeBucketed); ok { + if proto.Equal(policyTimeBucketed.PolicyTimeBucketed, &v1.RetentionPolicy_TimeBucketedCounts{}) { + err = multierror.Append(err, errors.New("time bucketed policy must specify a non-empty bucket")) + } } slices.Sort(plan.Paths) diff --git a/webui/src/state/oplog.ts b/webui/src/state/oplog.ts index 3fb9c72a..c72a228d 100644 --- a/webui/src/state/oplog.ts +++ b/webui/src/state/oplog.ts @@ -147,7 +147,7 @@ export class BackupInfoCollector { constructor( private filter: (op: Operation) => boolean = (op) => !shouldHideOperation(op), - ) {} + ) { } private createBackup(operations: Operation[]): BackupInfo { // deduplicate and sort operations. @@ -414,7 +414,7 @@ export const detailsForOperation = ( color = "grey"; break; case OperationStatus.STATUS_INPROGRESS: - state = "runnning"; + state = "running"; duration = new Date().getTime() - Number(op.unixTimeStartMs); color = "blue"; break; diff --git a/webui/src/views/AddPlanModal.tsx b/webui/src/views/AddPlanModal.tsx index cbad526c..369127e2 100644 --- a/webui/src/views/AddPlanModal.tsx +++ b/webui/src/views/AddPlanModal.tsx @@ -67,7 +67,7 @@ export const AddPlanModal = ({ template }: { template: Plan | null }) => { alertsApi.success( "Plan deleted from config, but not from restic repo. Snapshots will remain in storage and operations will be tracked until manually deleted. Reusing a deleted plan ID is not recommended if backups have already been performed.", - 30, + 30 ); } catch (e: any) { alertsApi.error("Operation failed: " + e.message, 15); @@ -492,18 +492,6 @@ const RetentionPolicyView = () => { const form = Form.useFormInstance(); const retention = Form.useWatch("retention", { form, preserve: true }) as any; - if (!retention) { - form.setFieldValue("retention", { - policyTimeBucketed: { - yearly: 0, - monthly: 3, - weekly: 4, - daily: 7, - hourly: 24, - }, - }); - } - const determineMode = () => { if (!retention) { return "policyTimeBucketed";