diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go
index ef5b7a61a..3aa1d6171 100644
--- a/internal/orchestrator/orchestrator.go
+++ b/internal/orchestrator/orchestrator.go
@@ -117,7 +117,7 @@ func (o *Orchestrator) Run(mainCtx context.Context) error {
// runImmutable is a helper function for Run() that runs the orchestration loop with a single version of the config.
func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bool {
- lock := sync.Mutex{}
+ var lock sync.Mutex
ctx, cancel := context.WithCancel(mainCtx)
var wg sync.WaitGroup
@@ -140,7 +140,9 @@ func (o *Orchestrator) runVersion(mainCtx context.Context, config *v1.Config) bo
defer wg.Done()
select {
case <-ctx.Done():
- timer.Stop()
+ if !timer.Stop() {
+ <-timer.C
+ }
zap.L().Debug("cancelled scheduled (but not running) task, orchestrator context is cancelled.", zap.String("task", t.Name()))
return
case <-timer.C:
diff --git a/internal/orchestrator/repo.go b/internal/orchestrator/repo.go
index 366a98b64..84a9dede9 100644
--- a/internal/orchestrator/repo.go
+++ b/internal/orchestrator/repo.go
@@ -19,12 +19,6 @@ type RepoOrchestrator struct {
repoConfig *v1.Repo
repo *restic.Repo
-
- // TODO: decide if snapshot caching is a good idea. We gain performance but
- // increase background memory use by a small amount at all times (probably on the order of 1MB).
- snapshotsMu sync.Mutex // enable very fast snapshot access IF no update is required.
- snapshotsResetTimer *time.Timer
- snapshots []*restic.Snapshot
}
func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrator {
@@ -34,64 +28,26 @@ func newRepoOrchestrator(repoConfig *v1.Repo, repo *restic.Repo) *RepoOrchestrat
}
}
-func (r *RepoOrchestrator) updateSnapshotsIfNeeded(ctx context.Context, force bool) error {
- if r.snapshots != nil {
- return nil
- }
-
- if r.snapshotsResetTimer != nil {
- if !r.snapshotsResetTimer.Stop() {
- <-r.snapshotsResetTimer.C
- }
- }
-
- r.snapshotsResetTimer = time.AfterFunc(10 * time.Minute, func() {
- r.snapshotsMu.Lock()
- defer r.snapshotsMu.Unlock()
- r.snapshots = nil
- })
-
- if r.snapshots != nil {
- return nil
- }
-
-
- startTime := time.Now()
-
+func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) {
snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...), restic.WithFlags("--latest", "1000"))
if err != nil {
- return fmt.Errorf("failed to update snapshots: %w", err)
+ return nil, fmt.Errorf("restic.Snapshots: %w", err)
}
sort.SliceStable(snapshots, func(i, j int) bool {
return snapshots[i].UnixTimeMs() < snapshots[j].UnixTimeMs()
})
- r.snapshots = snapshots
-
- zap.L().Debug("updated snapshots", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
-
- return nil
-}
-
-func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) {
- r.snapshotsMu.Lock()
- defer r.snapshotsMu.Unlock()
- if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil {
- return nil, err
- }
- return r.snapshots, nil
+ return snapshots, nil
}
func (r *RepoOrchestrator) SnapshotsForPlan(ctx context.Context, plan *v1.Plan) ([]*restic.Snapshot, error) {
- r.snapshotsMu.Lock()
- defer r.snapshotsMu.Unlock()
-
- if err := r.updateSnapshotsIfNeeded(ctx, false); err != nil {
+ snapshots, err := r.Snapshots(ctx)
+ if err != nil {
return nil, err
}
- return filterSnapshotsForPlan(r.snapshots, plan), nil
+ return filterSnapshotsForPlan(snapshots, plan), nil
}
func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCallback func(event *restic.BackupProgressEntry)) (*restic.BackupProgressEntry, error) {
@@ -123,14 +79,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa
return nil, fmt.Errorf("failed to backup: %w", err)
}
- // Reset snapshots since a new backup has been added.
- r.snapshotsMu.Lock()
- r.snapshots = nil
- r.snapshotsMu.Unlock()
-
zap.L().Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
-
-
return summary, nil
}
diff --git a/webui/src/components/OperationList.tsx b/webui/src/components/OperationList.tsx
index 6963d5d05..f55e89148 100644
--- a/webui/src/components/OperationList.tsx
+++ b/webui/src/components/OperationList.tsx
@@ -17,10 +17,6 @@ export const OperationList = ({
}: React.PropsWithoutRef<{ operations: EOperation[] }>) => {
operations.sort((a, b) => b.parsedTime - a.parsedTime);
- const elems = operations.map((operation) => (
-
- ));
-
if (operations.length === 0) {
return (
(
)}
+ pagination={
+ operations.length > 50
+ ? { position: "both", align: "center", defaultPageSize: 50 }
+ : {}
+ }
/>
);
};
@@ -68,7 +69,10 @@ export const OperationRow = ({
const backupOp = operation.operationBackup;
let desc = `Backup at ${formatTime(operation.unixTimeStartMs!)}`;
if (operation.status !== OperationStatus.STATUS_INPROGRESS) {
- desc += ` and finished at ${formatTime(operation.unixTimeEndMs!)}`;
+ desc += ` completed in ${formatDuration(
+ parseInt(operation.unixTimeEndMs!) -
+ parseInt(operation.unixTimeStartMs!)
+ )}`;
} else {
desc += " and is still running.";
}
@@ -273,5 +277,17 @@ const formatTime = (time: number | string) => {
}
const d = new Date();
d.setTime(time);
- return d.toLocaleString();
+ return d.toISOString();
};
+
+const formatDuration = (ms: number) => {
+ const seconds = Math.floor(ms / 100) / 10;
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ if (hours === 0 && minutes === 0) {
+ return `${seconds % 60}s`;
+ } else if (hours === 0) {
+ return `${minutes}m${seconds % 60}s`;
+ }
+ return `${hours}h${minutes % 60}m${seconds % 60}s`;
+};
\ No newline at end of file