mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
refactor(ui): use dot for backup status
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -43,3 +43,5 @@ node_modules/
|
|||||||
mutagen.yml.lock
|
mutagen.yml.lock
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ export function generateBreadcrumbs(pathname: string, params: Record<string, str
|
|||||||
isCurrentPage: pathname === "/backups",
|
isCurrentPage: pathname === "/backups",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pathname.startsWith("/backups/") && params.scheduleId) {
|
if (pathname.startsWith("/backups/") && params.id) {
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
label: `Schedule #${params.scheduleId}`,
|
label: `Schedule #${params.id}`,
|
||||||
isCurrentPage: true,
|
isCurrentPage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/tooltip";
|
||||||
|
|
||||||
|
type BackupStatus = "active" | "paused" | "error";
|
||||||
|
|
||||||
|
export const BackupStatusDot = ({ enabled, hasError }: { enabled: boolean; hasError?: boolean }) => {
|
||||||
|
let status: BackupStatus = "paused";
|
||||||
|
if (hasError) {
|
||||||
|
status = "error";
|
||||||
|
} else if (enabled) {
|
||||||
|
status = "active";
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMapping = {
|
||||||
|
active: {
|
||||||
|
color: "bg-green-500",
|
||||||
|
colorLight: "bg-emerald-400",
|
||||||
|
animated: true,
|
||||||
|
label: "Active",
|
||||||
|
},
|
||||||
|
paused: {
|
||||||
|
color: "bg-gray-500",
|
||||||
|
colorLight: "bg-gray-400",
|
||||||
|
animated: false,
|
||||||
|
label: "Paused",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: "bg-red-500",
|
||||||
|
colorLight: "bg-red-400",
|
||||||
|
animated: true,
|
||||||
|
label: "Error",
|
||||||
|
},
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<span className="relative flex size-3 mx-auto">
|
||||||
|
{statusMapping.animated && (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75",
|
||||||
|
`${statusMapping.colorLight}`,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className={cn("relative inline-flex size-3 rounded-full", `${statusMapping.color}`)} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{statusMapping.label}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,8 +3,8 @@ import { CalendarClock, Database, HardDrive, Plus } from "lucide-react";
|
|||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { listBackupSchedules } from "~/api-client";
|
import { listBackupSchedules } from "~/api-client";
|
||||||
import { listBackupSchedulesOptions } from "~/api-client/@tanstack/react-query.gen";
|
import { listBackupSchedulesOptions } from "~/api-client/@tanstack/react-query.gen";
|
||||||
|
import { BackupStatusDot } from "../components/backup-status-dot";
|
||||||
import { EmptyState } from "~/components/empty-state";
|
import { EmptyState } from "~/components/empty-state";
|
||||||
import { Badge } from "~/components/ui/badge";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import type { Route } from "./+types/backups";
|
import type { Route } from "./+types/backups";
|
||||||
@@ -73,9 +73,7 @@ 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>
|
||||||
<Badge variant={schedule.enabled ? "default" : "secondary"} className="flex-shrink-0">
|
<BackupStatusDot enabled={schedule.enabled} hasError={!!schedule.lastBackupError} />
|
||||||
{schedule.enabled ? "Active" : "Paused"}
|
|
||||||
</Badge>
|
|
||||||
</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" />
|
||||||
@@ -100,15 +98,10 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
|
|||||||
{schedule.nextBackupAt ? new Date(schedule.nextBackupAt).toLocaleDateString() : "N/A"}
|
{schedule.nextBackupAt ? new Date(schedule.nextBackupAt).toLocaleDateString() : "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{schedule.lastBackupStatus && (
|
{schedule.lastBackupError && (
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-start justify-between text-sm gap-2">
|
||||||
<span className="text-muted-foreground">Status</span>
|
<span className="text-muted-foreground">Error</span>
|
||||||
<Badge
|
<span className="text-xs text-red-600 text-right line-clamp-2">{schedule.lastBackupError}</span>
|
||||||
variant={schedule.lastBackupStatus === "success" ? "primary" : "destructive"}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
{schedule.lastBackupStatus}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user