fix: backup progress use simple polling & cache (#678)
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
#593
This commit is contained in:
parent
429b69ec92
commit
f6f17cd61c
5 changed files with 23 additions and 39 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ByteSize } from "~/client/components/bytes-size";
|
||||
import { Card } from "~/client/components/ui/card";
|
||||
import { Progress } from "~/client/components/ui/progress";
|
||||
import { useServerEvents } from "~/client/hooks/use-server-events";
|
||||
import { getBackupProgressOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import type { GetBackupProgressResponse } from "~/client/api-client/types.gen";
|
||||
import { formatDuration } from "~/utils/utils";
|
||||
import { formatBytes } from "~/utils/format-bytes";
|
||||
|
|
@ -13,34 +13,11 @@ type Props = {
|
|||
};
|
||||
|
||||
export const BackupProgressCard = ({ scheduleShortId, initialProgress }: Props) => {
|
||||
const { addEventListener } = useServerEvents();
|
||||
const [progress, setProgress] = useState<GetBackupProgressResponse>(initialProgress ?? null);
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
|
||||
addEventListener(
|
||||
"backup:progress",
|
||||
(progressData) => {
|
||||
if (progressData.scheduleId === scheduleShortId) {
|
||||
setProgress(progressData);
|
||||
}
|
||||
},
|
||||
{ signal: abortController.signal },
|
||||
);
|
||||
|
||||
addEventListener(
|
||||
"backup:completed",
|
||||
(completedData) => {
|
||||
if (completedData.scheduleId === scheduleShortId) {
|
||||
setProgress(null);
|
||||
}
|
||||
},
|
||||
{ signal: abortController.signal },
|
||||
);
|
||||
|
||||
return () => abortController.abort();
|
||||
}, [addEventListener, scheduleShortId]);
|
||||
const { data: progress } = useQuery({
|
||||
...getBackupProgressOptions({ path: { shortId: scheduleShortId } }),
|
||||
initialData: initialProgress,
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
const percentDone = progress ? Math.round(progress.percent_done * 100) : 0;
|
||||
const currentFile = progress?.current_files?.[0] || "";
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export function ScheduleDetailsPage(props: Props) {
|
|||
|
||||
const { data: schedule } = useSuspenseQuery({
|
||||
...getBackupScheduleOptions({ path: { shortId: scheduleId } }),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import { requireAuth } from "../auth/auth.middleware";
|
|||
import { backupsExecutionService } from "./backups.execution";
|
||||
import { logger } from "@zerobyte/core/node";
|
||||
import { asShortId } from "~/server/utils/branded";
|
||||
import { cache, cacheKeys } from "~/server/utils/cache";
|
||||
|
||||
export const backupScheduleController = new Hono()
|
||||
.use(requireAuth)
|
||||
|
|
@ -167,6 +168,10 @@ export const backupScheduleController = new Hono()
|
|||
.get("/:shortId/progress", getBackupProgressDto, async (c) => {
|
||||
const shortId = asShortId(c.req.param("shortId"));
|
||||
const schedule = await backupsService.getScheduleByShortId(shortId);
|
||||
if (schedule.lastBackupStatus !== "in_progress") {
|
||||
cache.del(cacheKeys.backup.progress(schedule.id));
|
||||
return c.json<GetBackupProgressDto>(null, 200);
|
||||
}
|
||||
const progress = backupsExecutionService.getBackupProgress(schedule.id);
|
||||
|
||||
return c.json<GetBackupProgressDto>(progress ?? null, 200);
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@ import type { ResticBackupOutputDto } from "@zerobyte/core/restic";
|
|||
import type { BackupProgressEventDto } from "~/schemas/events-dto";
|
||||
|
||||
const runningBackups = new Map<number, AbortController>();
|
||||
const progressCache = new Map<number, BackupProgressEventDto>();
|
||||
|
||||
export const getBackupProgress = (scheduleId: number): BackupProgressEventDto | undefined =>
|
||||
progressCache.get(scheduleId);
|
||||
cache.get<BackupProgressEventDto>(cacheKeys.backup.progress(scheduleId));
|
||||
|
||||
interface BackupContext {
|
||||
schedule: BackupSchedule;
|
||||
|
|
@ -123,18 +122,16 @@ const runBackupOperation = async (ctx: BackupContext, signal: AbortSignal) => {
|
|||
compressionMode: ctx.repository.compressionMode ?? "auto",
|
||||
organizationId: ctx.organizationId,
|
||||
onProgress: (progress) => {
|
||||
progressCache.set(ctx.schedule.id, {
|
||||
const progressEvent = {
|
||||
scheduleId: ctx.schedule.shortId,
|
||||
volumeName: ctx.volume.name,
|
||||
repositoryName: ctx.repository.name,
|
||||
...progress,
|
||||
});
|
||||
};
|
||||
cache.set(cacheKeys.backup.progress(ctx.schedule.id), progressEvent, 60 * 60);
|
||||
serverEvents.emit("backup:progress", {
|
||||
organizationId: ctx.organizationId,
|
||||
scheduleId: ctx.schedule.shortId,
|
||||
volumeName: ctx.volume.name,
|
||||
repositoryName: ctx.repository.name,
|
||||
...progress,
|
||||
...progressEvent,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -271,6 +268,7 @@ const executeBackup = async (scheduleId: number, manual = false): Promise<void>
|
|||
}
|
||||
|
||||
const { context: ctx } = result;
|
||||
cache.del(cacheKeys.backup.progress(scheduleId));
|
||||
emitBackupStarted(ctx, scheduleId);
|
||||
|
||||
const nextBackupAt = calculateNextRun(ctx.schedule.cronExpression);
|
||||
|
|
@ -297,7 +295,7 @@ const executeBackup = async (scheduleId: number, manual = false): Promise<void>
|
|||
await handleBackupFailure(scheduleId, ctx.organizationId, error, ctx);
|
||||
} finally {
|
||||
runningBackups.delete(scheduleId);
|
||||
progressCache.delete(scheduleId);
|
||||
cache.del(cacheKeys.backup.progress(scheduleId));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ export const createCache = (options: CacheOptions = {}) => {
|
|||
};
|
||||
|
||||
export const cacheKeys = {
|
||||
backup: {
|
||||
progress: (scheduleId: number) => `backup:${scheduleId}:progress`,
|
||||
},
|
||||
repository: {
|
||||
all: (repositoryId: string) => `repo:${repositoryId}:`,
|
||||
snapshots: (repositoryId: string, backupId = "all") => `repo:${repositoryId}:snapshots:${backupId}`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue