refactor: pr feedbacks

This commit is contained in:
Nicolas Meienberger 2026-02-12 18:30:21 +01:00
parent d5284d9450
commit 7746cf3608
7 changed files with 67 additions and 19 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import type { import type {
BackupCompletedEventDto, BackupCompletedEventDto,
@ -205,7 +205,7 @@ export function useServerEvents() {
}; };
}, [queryClient]); }, [queryClient]);
const addEventListener = (event: ServerEventType, handler: EventHandler) => { const addEventListener = useCallback((event: ServerEventType, handler: EventHandler) => {
if (!handlersRef.current.has(event)) { if (!handlersRef.current.has(event)) {
handlersRef.current.set(event, new Set()); handlersRef.current.set(event, new Set());
} }
@ -214,7 +214,7 @@ export function useServerEvents() {
return () => { return () => {
handlersRef.current.get(event)?.delete(handler); handlersRef.current.get(event)?.delete(handler);
}; };
}; }, []);
return { addEventListener }; return { addEventListener };
} }

View file

@ -356,7 +356,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<StatusDot <StatusDot
variant={getStatusVariant(assignment.lastCopyStatus)} variant={getStatusVariant(assignment.lastCopyStatus)}
label={getStatusLabel(assignment)} label={getStatusLabel(assignment)}
animated={isSyncing(assignment) ? true : undefined} animated={isSyncing(assignment)}
/> />
<span className="text-sm text-muted-foreground">{getLabel(assignment)}</span> <span className="text-sm text-muted-foreground">{getLabel(assignment)}</span>
</div> </div>

View file

@ -49,8 +49,25 @@ export function BackupsPage() {
if (over && active.id !== over.id) { if (over && active.id !== over.id) {
setLocalItems((currentItems) => { setLocalItems((currentItems) => {
const baseItems = currentItems ?? schedules?.map((s) => s.id) ?? []; const baseItems = currentItems ?? schedules?.map((s) => s.id) ?? [];
const oldIndex = baseItems.indexOf(active.id as number); const activeId = active.id as number;
const newIndex = baseItems.indexOf(over.id as number); const overId = over.id as number;
let oldIndex = baseItems.indexOf(activeId);
let newIndex = baseItems.indexOf(overId);
if (oldIndex === -1 || newIndex === -1) {
const freshItems = schedules?.map((s) => s.id) ?? [];
oldIndex = freshItems.indexOf(activeId);
newIndex = freshItems.indexOf(overId);
if (oldIndex === -1 || newIndex === -1) {
return currentItems;
}
const newItems = arrayMove(freshItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } });
return newItems;
}
const newItems = arrayMove(baseItems, oldIndex, newIndex); const newItems = arrayMove(baseItems, oldIndex, newIndex);
reorderMutation.mutate({ body: { scheduleIds: newItems } }); reorderMutation.mutate({ body: { scheduleIds: newItems } });

View file

@ -6,13 +6,6 @@ import { RepositoryInfoTabContent } from "../tabs/info";
import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
export const handle = {
breadcrumb: (match: Route.MetaArgs) => [
{ label: "Repositories", href: "/repositories" },
{ label: match.loaderData?.name || match.params.id },
],
};
export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: string }) { export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: string }) {
const navigate = useNavigate(); const navigate = useNavigate();
const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId" }); const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId" });

View file

@ -296,6 +296,37 @@ describe("mirror operations", () => {
expect(updatedMirror?.lastCopyAt).not.toBeNull(); expect(updatedMirror?.lastCopyAt).not.toBeNull();
}); });
test("should finalize mirror status when mirror settings are updated during copy", async () => {
// arrange
const volume = await createTestVolume();
const sourceRepository = await createTestRepository();
const mirrorRepository = await createTestRepository();
const schedule = await createTestBackupSchedule({
volumeId: volume.id,
repositoryId: sourceRepository.id,
});
const originalMirror = await createTestBackupScheduleMirror(schedule.id, mirrorRepository.id);
resticCopyMock.mockImplementationOnce(async () => {
await backupsService.updateMirrors(schedule.id, {
mirrors: [{ repositoryId: mirrorRepository.id, enabled: true }],
});
return { success: true, output: "" };
});
// act
await backupsExecutionService.copyToMirrors(schedule.id, sourceRepository, null);
// assert
const mirrors = await backupsService.getMirrors(schedule.id);
expect(mirrors).toHaveLength(1);
expect(mirrors[0]?.id).not.toBe(originalMirror.id);
expect(mirrors[0]?.lastCopyStatus).toBe("success");
expect(mirrors[0]?.lastCopyError).toBeNull();
expect(mirrors[0]?.lastCopyAt).not.toBeNull();
});
test("should update mirror status on failure", async () => { test("should update mirror status on failure", async () => {
// arrange // arrange
const volume = await createTestVolume(); const volume = await createTestVolume();

View file

@ -367,7 +367,6 @@ const copyToSingleMirror = async (
schedule: BackupSchedule, schedule: BackupSchedule,
sourceRepository: Repository, sourceRepository: Repository,
mirror: { mirror: {
id: number;
repositoryId: string; repositoryId: string;
repository: Repository; repository: Repository;
}, },
@ -384,7 +383,7 @@ const copyToSingleMirror = async (
repositoryName: mirror.repository.name, repositoryName: mirror.repository.name,
}); });
await mirrorQueries.updateStatus(mirror.id, { await mirrorQueries.updateStatus(scheduleId, mirror.repositoryId, {
lastCopyStatus: "in_progress", lastCopyStatus: "in_progress",
lastCopyError: null, lastCopyError: null,
}); });
@ -408,7 +407,7 @@ const copyToSingleMirror = async (
}); });
} }
await mirrorQueries.updateStatus(mirror.id, { await mirrorQueries.updateStatus(scheduleId, mirror.repositoryId, {
lastCopyAt: Date.now(), lastCopyAt: Date.now(),
lastCopyStatus: "success", lastCopyStatus: "success",
lastCopyError: null, lastCopyError: null,
@ -427,7 +426,7 @@ const copyToSingleMirror = async (
const errorMessage = toMessage(error); const errorMessage = toMessage(error);
logger.error(`[Background] Failed to copy to mirror repository ${mirror.repository.name}: ${errorMessage}`); logger.error(`[Background] Failed to copy to mirror repository ${mirror.repository.name}: ${errorMessage}`);
await mirrorQueries.updateStatus(mirror.id, { await mirrorQueries.updateStatus(scheduleId, mirror.repositoryId, {
lastCopyAt: Date.now(), lastCopyAt: Date.now(),
lastCopyStatus: "error", lastCopyStatus: "error",
lastCopyError: errorMessage, lastCopyError: errorMessage,

View file

@ -59,8 +59,16 @@ export const mirrorQueries = {
}); });
}, },
updateStatus: async (mirrorId: number, status: MirrorStatusUpdate) => { updateStatus: async (scheduleId: number, repositoryId: string, status: MirrorStatusUpdate) => {
return db.update(backupScheduleMirrorsTable).set(status).where(eq(backupScheduleMirrorsTable.id, mirrorId)); return db
.update(backupScheduleMirrorsTable)
.set(status)
.where(
and(
eq(backupScheduleMirrorsTable.scheduleId, scheduleId),
eq(backupScheduleMirrorsTable.repositoryId, repositoryId),
),
);
}, },
}; };