From d6b057f16616640a87025d4e4763c2b0a837dce8 Mon Sep 17 00:00:00 2001 From: Gareth George Date: Fri, 29 Dec 2023 02:29:13 +0000 Subject: [PATCH] feat: schedule index operations and stats refresh from repo view --- internal/orchestrator/taskprune.go | 37 ++++++++++++++++++++--------- internal/orchestrator/tasks.go | 2 +- internal/orchestrator/taskstats.go | 2 +- webui/src/api.ts | 1 + webui/src/components/SpinButton.tsx | 29 ++++++++++++++++++++++ webui/src/constants.ts | 2 ++ webui/src/state/oplog.ts | 5 ++-- webui/src/views/PlanView.tsx | 29 +++++++++++----------- webui/src/views/RepoView.tsx | 31 ++++++++++++++++++++---- 9 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 webui/src/components/SpinButton.tsx diff --git a/internal/orchestrator/taskprune.go b/internal/orchestrator/taskprune.go index 19df57f0..7cdaf36a 100644 --- a/internal/orchestrator/taskprune.go +++ b/internal/orchestrator/taskprune.go @@ -40,6 +40,14 @@ func (t *PruneTask) Name() string { } func (t *PruneTask) Next(now time.Time) *time.Time { + shouldRun, err := t.shouldRun(now) + if err != nil { + zap.S().Errorf("task %v failed to check if it should run: %v", t.Name(), err) + } + if !shouldRun { + return nil + } + ret := t.at if ret != nil { t.at = nil @@ -58,6 +66,24 @@ func (t *PruneTask) Next(now time.Time) *time.Time { return ret } +func (t *PruneTask) shouldRun(now time.Time) (bool, error) { + if t.force { + return true, nil + } + + repo, err := t.orch.GetRepo(t.plan.Repo) + if err != nil { + return false, fmt.Errorf("get repo %v: %w", t.plan.Repo, err) + } + + nextPruneTime, err := t.getNextPruneTime(repo, repo.repoConfig.PrunePolicy) + if err != nil { + return false, fmt.Errorf("get next prune time: %w", err) + } + + return nextPruneTime.Before(now), nil +} + func (t *PruneTask) getNextPruneTime(repo *RepoOrchestrator, policy *v1.PrunePolicy) (time.Time, error) { var lastPruneTime time.Time t.orch.OpLog.ForEachByRepo(t.plan.Repo, indexutil.CollectLastN(100), func(op *v1.Operation) error { @@ -86,17 +112,6 @@ func (t *PruneTask) Run(ctx context.Context) error { } op.Op = opPrune - if !t.force { - nextPruneTime, err := t.getNextPruneTime(repo, repo.repoConfig.PrunePolicy) - if err != nil { - return fmt.Errorf("get next prune time: %w", err) - } - if nextPruneTime.After(time.Now()) { - op.Status = v1.OperationStatus_STATUS_SYSTEM_CANCELLED - return nil - } - } - ctx, cancel := context.WithCancel(ctx) interval := time.NewTicker(1 * time.Second) defer interval.Stop() diff --git a/internal/orchestrator/tasks.go b/internal/orchestrator/tasks.go index a0418099..8c3e420d 100644 --- a/internal/orchestrator/tasks.go +++ b/internal/orchestrator/tasks.go @@ -46,7 +46,7 @@ func (t *TaskWithOperation) setOperation(op *v1.Operation) error { func (t *TaskWithOperation) runWithOpAndContext(ctx context.Context, do func(ctx context.Context, op *v1.Operation) error) error { if t.op == nil { - return errors.New("task has no operation, a call to setOperation first is required.") + return errors.New("task has no operation, a call to setOperation first is required") } if t.running.Load() { return errors.New("task is already running") diff --git a/internal/orchestrator/taskstats.go b/internal/orchestrator/taskstats.go index c63530f3..d696475a 100644 --- a/internal/orchestrator/taskstats.go +++ b/internal/orchestrator/taskstats.go @@ -11,7 +11,7 @@ import ( "go.uber.org/zap" ) -var statBytesThreshold int64 = 1024 * 1024 * 1024 // 1GB +var statBytesThreshold int64 = 10 * 1024 * 1024 * 1024 // 10 GB added. // StatsTask tracks a restic stats operation. type StatsTask struct { diff --git a/webui/src/api.ts b/webui/src/api.ts index 7e27a4fc..ce6bb909 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -5,6 +5,7 @@ import { Backrest } from "../gen/ts/v1/service_connect"; const transport = createConnectTransport({ baseUrl: "/", + useBinaryFormat: true, }); export const backrestService = createPromiseClient(Backrest, transport); diff --git a/webui/src/components/SpinButton.tsx b/webui/src/components/SpinButton.tsx new file mode 100644 index 00000000..0d4ae98e --- /dev/null +++ b/webui/src/components/SpinButton.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Button, ButtonProps } from "antd"; +import { useState } from "react"; + +export const SpinButton: React.FC Promise; +}> = ({ onClickAsync, ...props }) => { + const [loading, setLoading] = useState(false); + + const onClick = async () => { + if (loading) { + return; + } + try { + setLoading(true); + await onClickAsync(); + } finally { + setLoading(false); + } + }; + + return ( + + - + - + - + ) => { const alertsApi = useAlertApi()!; @@ -21,7 +24,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { useEffect(() => { setLoading(true); setStatsOperation(null); - getOperations(new GetOperationsRequest({ repoId: repo.id!, lastN: BigInt(100) })).then((operations) => { + getOperations(new GetOperationsRequest({ repoId: repo.id!, lastN: BigInt(STATS_OPERATION_HISTORY) })).then((operations) => { for (const op of operations) { if (op.op.case === "operationStats") { const stats = op.op.value.stats; @@ -37,6 +40,11 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { }); }, [repo.id]); + // Task handlers + const handleIndexNow = async () => { + await backrestService.indexSnapshots(new StringValue({ value: repo.id! })); + } + // Gracefully handle deletions by checking if the plan is still in the config. const config = useRecoilValue(configState); let repoInConfig = config.repos?.find((p) => p.id === repo.id); @@ -64,6 +72,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { } ), + destroyInactiveTabPane: true, }, { key: "2", @@ -76,6 +85,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { /> ), + destroyInactiveTabPane: true, }, { key: "3", @@ -89,6 +99,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { /> ), + destroyInactiveTabPane: true, }, ] return ( @@ -98,6 +109,18 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => { {repo.id} + + + + Index Snapshots + + + + + Index Snapshots + + +