mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: backup in progress status
This commit is contained in:
@@ -965,7 +965,7 @@ export type ListBackupSchedulesResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: "error" | "success" | null;
|
lastBackupStatus: "error" | "in_progress" | "success" | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
||||||
@@ -1087,7 +1087,7 @@ export type CreateBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: "error" | "success" | null;
|
lastBackupStatus: "error" | "in_progress" | "success" | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
@@ -1148,7 +1148,7 @@ export type GetBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: "error" | "success" | null;
|
lastBackupStatus: "error" | "in_progress" | "success" | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
||||||
@@ -1271,7 +1271,7 @@ export type UpdateBackupScheduleResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: "error" | "success" | null;
|
lastBackupStatus: "error" | "in_progress" | "success" | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repositoryId: string;
|
repositoryId: string;
|
||||||
retentionPolicy: {
|
retentionPolicy: {
|
||||||
@@ -1312,7 +1312,7 @@ export type GetBackupScheduleForVolumeResponses = {
|
|||||||
includePatterns: Array<string> | null;
|
includePatterns: Array<string> | null;
|
||||||
lastBackupAt: number | null;
|
lastBackupAt: number | null;
|
||||||
lastBackupError: string | null;
|
lastBackupError: string | null;
|
||||||
lastBackupStatus: "error" | "success" | null;
|
lastBackupStatus: "error" | "in_progress" | "success" | null;
|
||||||
nextBackupAt: number | null;
|
nextBackupAt: number | null;
|
||||||
repository: {
|
repository: {
|
||||||
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
compressionMode: "auto" | "better" | "fastest" | "max" | "off" | null;
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
||||||
|
|
||||||
type BackupStatus = "active" | "paused" | "error";
|
type BackupStatus = "active" | "paused" | "error" | "in_progress";
|
||||||
|
|
||||||
export const BackupStatusDot = ({ enabled, hasError }: { enabled: boolean; hasError?: boolean }) => {
|
export const BackupStatusDot = ({
|
||||||
|
enabled,
|
||||||
|
hasError,
|
||||||
|
isInProgress,
|
||||||
|
}: { enabled: boolean; hasError?: boolean; isInProgress?: boolean }) => {
|
||||||
let status: BackupStatus = "paused";
|
let status: BackupStatus = "paused";
|
||||||
if (hasError) {
|
if (isInProgress) {
|
||||||
|
status = "in_progress";
|
||||||
|
} else if (hasError) {
|
||||||
status = "error";
|
status = "error";
|
||||||
} else if (enabled) {
|
} else if (enabled) {
|
||||||
status = "active";
|
status = "active";
|
||||||
@@ -30,6 +36,12 @@ export const BackupStatusDot = ({ enabled, hasError }: { enabled: boolean; hasEr
|
|||||||
animated: true,
|
animated: true,
|
||||||
label: "Error",
|
label: "Error",
|
||||||
},
|
},
|
||||||
|
in_progress: {
|
||||||
|
color: "bg-blue-500",
|
||||||
|
colorLight: "bg-blue-400",
|
||||||
|
animated: true,
|
||||||
|
label: "Backup in progress",
|
||||||
|
},
|
||||||
}[status];
|
}[status];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -75,9 +75,15 @@ export const ScheduleSummary = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row gap-2">
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
<Button variant="default" size="sm" onClick={handleRunBackupNow} className="w-full sm:w-auto">
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleRunBackupNow}
|
||||||
|
disabled={schedule.lastBackupStatus === "in_progress"}
|
||||||
|
className="w-full sm:w-auto"
|
||||||
|
>
|
||||||
<Play className="h-4 w-4 mr-2" />
|
<Play className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Backup Now</span>
|
<span className="sm:inline">Backup now</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" onClick={() => setIsEditMode(true)} className="w-full sm:w-auto">
|
<Button variant="outline" size="sm" onClick={() => setIsEditMode(true)} className="w-full sm:w-auto">
|
||||||
<Pencil className="h-4 w-4 mr-2" />
|
<Pencil className="h-4 w-4 mr-2" />
|
||||||
@@ -121,6 +127,7 @@ export const ScheduleSummary = (props: Props) => {
|
|||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{schedule.lastBackupStatus === "success" && "✓ Success"}
|
{schedule.lastBackupStatus === "success" && "✓ Success"}
|
||||||
{schedule.lastBackupStatus === "error" && "✗ Error"}
|
{schedule.lastBackupStatus === "error" && "✗ Error"}
|
||||||
|
{schedule.lastBackupStatus === "in_progress" && "⟳ in progress..."}
|
||||||
{!schedule.lastBackupStatus && "—"}
|
{!schedule.lastBackupStatus && "—"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
|||||||
path: { scheduleId: params.id },
|
path: { scheduleId: params.id },
|
||||||
}),
|
}),
|
||||||
initialData: loaderData,
|
initialData: loaderData,
|
||||||
|
refetchInterval: 10000,
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
|
|||||||
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
|
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<BackupStatusDot enabled={schedule.enabled} hasError={!!schedule.lastBackupError} />
|
<BackupStatusDot
|
||||||
|
enabled={schedule.enabled}
|
||||||
|
hasError={!!schedule.lastBackupError}
|
||||||
|
isInProgress={schedule.lastBackupStatus === "in_progress"}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className="flex items-center gap-2 mt-2">
|
<CardDescription className="flex items-center gap-2 mt-2">
|
||||||
<Database className="h-4 w-4" />
|
<Database className="h-4 w-4" />
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
|
|||||||
excludePatterns: text("exclude_patterns", { mode: "json" }).$type<string[]>().default([]),
|
excludePatterns: text("exclude_patterns", { mode: "json" }).$type<string[]>().default([]),
|
||||||
includePatterns: text("include_patterns", { mode: "json" }).$type<string[]>().default([]),
|
includePatterns: text("include_patterns", { mode: "json" }).$type<string[]>().default([]),
|
||||||
lastBackupAt: int("last_backup_at", { mode: "number" }),
|
lastBackupAt: int("last_backup_at", { mode: "number" }),
|
||||||
lastBackupStatus: text("last_backup_status").$type<"success" | "error">(),
|
lastBackupStatus: text("last_backup_status").$type<"success" | "error" | "in_progress">(),
|
||||||
lastBackupError: text("last_backup_error"),
|
lastBackupError: text("last_backup_error"),
|
||||||
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
nextBackupAt: int("next_backup_at", { mode: "number" }),
|
||||||
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
createdAt: int("created_at", { mode: "number" }).notNull().default(sql`(unixepoch())`),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const backupScheduleSchema = type({
|
|||||||
excludePatterns: "string[] | null",
|
excludePatterns: "string[] | null",
|
||||||
includePatterns: "string[] | null",
|
includePatterns: "string[] | null",
|
||||||
lastBackupAt: "number | null",
|
lastBackupAt: "number | null",
|
||||||
lastBackupStatus: "'success' | 'error' | null",
|
lastBackupStatus: "'success' | 'error' | 'in_progress' | null",
|
||||||
lastBackupError: "string | null",
|
lastBackupError: "string | null",
|
||||||
nextBackupAt: "number | null",
|
nextBackupAt: "number | null",
|
||||||
createdAt: "number",
|
createdAt: "number",
|
||||||
|
|||||||
@@ -181,6 +181,11 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
|||||||
|
|
||||||
logger.info(`Starting backup for volume ${volume.name} to repository ${repository.name}`);
|
logger.info(`Starting backup for volume ${volume.name} to repository ${repository.name}`);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(backupSchedulesTable)
|
||||||
|
.set({ lastBackupStatus: "in_progress", updatedAt: Date.now() })
|
||||||
|
.where(eq(backupSchedulesTable.id, scheduleId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const volumePath = getVolumePath(volume.name);
|
const volumePath = getVolumePath(volume.name);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user