mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-12 16:55:39 +00:00
fix: UI buttons spin while waiting for tasks to complete
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
@@ -268,8 +269,14 @@ func (s *Server) Backup(ctx context.Context, req *connect.Request[types.StringVa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err)
|
return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err)
|
||||||
}
|
}
|
||||||
s.orchestrator.ScheduleTask(orchestrator.NewOneoffBackupTask(s.orchestrator, plan, time.Now()), orchestrator.TaskPriorityInteractive)
|
var wg sync.WaitGroup
|
||||||
return connect.NewResponse(&emptypb.Empty{}), nil
|
wg.Add(1)
|
||||||
|
s.orchestrator.ScheduleTask(orchestrator.NewOneoffBackupTask(s.orchestrator, plan, time.Now()), orchestrator.TaskPriorityInteractive, func(e error) {
|
||||||
|
err = e
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
return connect.NewResponse(&emptypb.Empty{}), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
|
func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
|
||||||
@@ -279,11 +286,15 @@ func (s *Server) Forget(ctx context.Context, req *connect.Request[types.StringVa
|
|||||||
}
|
}
|
||||||
|
|
||||||
at := time.Now()
|
at := time.Now()
|
||||||
|
var wg sync.WaitGroup
|
||||||
s.orchestrator.ScheduleTask(orchestrator.NewOneoffForgetTask(s.orchestrator, plan, "", at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityForget)
|
wg.Add(1)
|
||||||
|
s.orchestrator.ScheduleTask(orchestrator.NewOneoffForgetTask(s.orchestrator, plan, "", at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityForget, func(e error) {
|
||||||
|
err = e
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
s.orchestrator.ScheduleTask(orchestrator.NewOneoffIndexSnapshotsTask(s.orchestrator, plan.Repo, at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityIndexSnapshots)
|
s.orchestrator.ScheduleTask(orchestrator.NewOneoffIndexSnapshotsTask(s.orchestrator, plan.Repo, at), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityIndexSnapshots)
|
||||||
|
wg.Wait()
|
||||||
return connect.NewResponse(&emptypb.Empty{}), nil
|
return connect.NewResponse(&emptypb.Empty{}), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
|
func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) {
|
||||||
@@ -293,7 +304,13 @@ func (s *Server) Prune(ctx context.Context, req *connect.Request[types.StringVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
at := time.Now()
|
at := time.Now()
|
||||||
s.orchestrator.ScheduleTask(orchestrator.NewOneoffPruneTask(s.orchestrator, plan, "", at, true), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityPrune)
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
s.orchestrator.ScheduleTask(orchestrator.NewOneoffPruneTask(s.orchestrator, plan, "", at, true), orchestrator.TaskPriorityInteractive+orchestrator.TaskPriorityPrune, func(e error) {
|
||||||
|
err = e
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
return connect.NewResponse(&emptypb.Empty{}), nil
|
return connect.NewResponse(&emptypb.Empty{}), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,14 +230,18 @@ func (o *Orchestrator) Run(mainCtx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := t.task.Run(taskCtx); err != nil {
|
err := t.task.Run(taskCtx)
|
||||||
zap.L().Error("task failed", zap.String("task", t.task.Name()), zap.Error(err))
|
if err != nil {
|
||||||
|
zap.L().Error("task failed", zap.String("task", t.task.Name()), zap.Error(err), zap.Duration("duration", time.Since(start)))
|
||||||
} else {
|
} else {
|
||||||
zap.L().Info("task finished", zap.String("task", t.task.Name()), zap.Duration("duration", time.Since(start)))
|
zap.L().Info("task finished", zap.String("task", t.task.Name()), zap.Duration("duration", time.Since(start)))
|
||||||
}
|
}
|
||||||
|
|
||||||
o.runningTask.Store(nil)
|
o.runningTask.Store(nil)
|
||||||
|
|
||||||
|
for _, cb := range t.callbacks {
|
||||||
|
cb(err)
|
||||||
|
}
|
||||||
|
|
||||||
if nextTime := t.task.Next(o.curTime()); nextTime != nil {
|
if nextTime := t.task.Next(o.curTime()); nextTime != nil {
|
||||||
o.taskQueue.Push(scheduledTask{
|
o.taskQueue.Push(scheduledTask{
|
||||||
task: t.task,
|
task: t.task,
|
||||||
@@ -247,7 +251,7 @@ func (o *Orchestrator) Run(mainCtx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Orchestrator) ScheduleTask(t Task, priority int) {
|
func (o *Orchestrator) ScheduleTask(t Task, priority int, callbacks ...func(error)) {
|
||||||
nextRun := t.Next(o.curTime())
|
nextRun := t.Next(o.curTime())
|
||||||
if nextRun == nil {
|
if nextRun == nil {
|
||||||
return
|
return
|
||||||
@@ -257,6 +261,7 @@ func (o *Orchestrator) ScheduleTask(t Task, priority int) {
|
|||||||
task: t,
|
task: t,
|
||||||
runAt: *nextRun,
|
runAt: *nextRun,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
|
callbacks: callbacks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ type scheduledTask struct {
|
|||||||
task Task
|
task Task
|
||||||
runAt time.Time
|
runAt time.Time
|
||||||
priority int
|
priority int
|
||||||
|
callbacks []func(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type scheduledTaskHeap struct {
|
type scheduledTaskHeap struct {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
|
|||||||
|
|
||||||
const handleBackupNow = async () => {
|
const handleBackupNow = async () => {
|
||||||
try {
|
try {
|
||||||
backrestService.backup({ value: plan.id });
|
await backrestService.backup({ value: plan.id });
|
||||||
alertsApi.success("Backup scheduled.");
|
alertsApi.success("Backup scheduled.");
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alertsApi.error("Failed to schedule backup: " + e.message);
|
alertsApi.error("Failed to schedule backup: " + e.message);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
|
|||||||
<>
|
<>
|
||||||
<h3>Repo stats computed on {formatTime(Number(statsOperation.unixTimeStartMs))}</h3>
|
<h3>Repo stats computed on {formatTime(Number(statsOperation.unixTimeStartMs))}</h3>
|
||||||
{statsOperation.op.case === "operationStats" && <StatsTable stats={statsOperation.op.value.stats!} />}
|
{statsOperation.op.case === "operationStats" && <StatsTable stats={statsOperation.op.value.stats!} />}
|
||||||
<small>Stats are refreshed periodically in the background as new data is added.</small>
|
<small>Stats are refreshed periodically in the background as new data is added (e.g. every 10GB added or every 50 operations).</small>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user