mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
refactor(breadcrumbs): use handler & match pattern
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Link } from "react-router";
|
||||
import { Link, useMatches, type UIMatch } from "react-router";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -7,14 +7,38 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "~/client/components/ui/breadcrumb";
|
||||
import { useBreadcrumbs } from "~/client/lib/breadcrumbs";
|
||||
|
||||
export interface BreadcrumbItemData {
|
||||
label: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
interface RouteHandle {
|
||||
breadcrumb?: (match: UIMatch) => BreadcrumbItemData[] | null;
|
||||
}
|
||||
|
||||
export function AppBreadcrumb() {
|
||||
const breadcrumbs = useBreadcrumbs();
|
||||
const matches = useMatches();
|
||||
|
||||
// Find the last match with a breadcrumb handler
|
||||
const lastMatchWithBreadcrumb = [...matches].reverse().find((match) => {
|
||||
const handle = match.handle as RouteHandle | undefined;
|
||||
return handle?.breadcrumb;
|
||||
});
|
||||
|
||||
if (!lastMatchWithBreadcrumb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handle = lastMatchWithBreadcrumb.handle as RouteHandle;
|
||||
const breadcrumbs = handle.breadcrumb?.(lastMatchWithBreadcrumb);
|
||||
|
||||
if (!breadcrumbs || breadcrumbs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbLink asChild></BreadcrumbLink>
|
||||
<BreadcrumbList>
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
const isLast = index === breadcrumbs.length - 1;
|
||||
@@ -22,14 +46,12 @@ export function AppBreadcrumb() {
|
||||
return (
|
||||
<div key={`${breadcrumb.label}-${index}`} className="contents">
|
||||
<BreadcrumbItem>
|
||||
{isLast || breadcrumb.isCurrentPage ? (
|
||||
{isLast || !breadcrumb.href ? (
|
||||
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
|
||||
) : breadcrumb.href ? (
|
||||
) : (
|
||||
<BreadcrumbLink asChild>
|
||||
<Link to={breadcrumb.href}>{breadcrumb.label}</Link>
|
||||
</BreadcrumbLink>
|
||||
) : (
|
||||
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
{!isLast && <BreadcrumbSeparator />}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { useLocation, useParams } from "react-router";
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
label: string;
|
||||
href?: string;
|
||||
isCurrentPage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates breadcrumb items based on the current route
|
||||
* @param pathname - Current pathname from useLocation
|
||||
* @param params - Route parameters from useParams
|
||||
* @returns Array of breadcrumb items
|
||||
*/
|
||||
export function generateBreadcrumbs(pathname: string, params: Record<string, string | undefined>): BreadcrumbItem[] {
|
||||
const breadcrumbs: BreadcrumbItem[] = [];
|
||||
|
||||
if (pathname.startsWith("/repositories")) {
|
||||
breadcrumbs.push({
|
||||
label: "Repositories",
|
||||
href: "/repositories",
|
||||
isCurrentPage: pathname === "/repositories",
|
||||
});
|
||||
|
||||
if (pathname.startsWith("/repositories/") && params.name) {
|
||||
const isSnapshotPage = !!params.snapshotId;
|
||||
|
||||
breadcrumbs.push({
|
||||
label: params.name,
|
||||
href: isSnapshotPage ? `/repositories/${params.name}` : undefined,
|
||||
isCurrentPage: !isSnapshotPage,
|
||||
});
|
||||
|
||||
if (isSnapshotPage && params.snapshotId) {
|
||||
breadcrumbs.push({
|
||||
label: params.snapshotId,
|
||||
isCurrentPage: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/backups")) {
|
||||
breadcrumbs.push({
|
||||
label: "Backups",
|
||||
href: "/backups",
|
||||
isCurrentPage: pathname === "/backups",
|
||||
});
|
||||
|
||||
if (pathname.startsWith("/backups/") && params.id) {
|
||||
breadcrumbs.push({
|
||||
label: `Schedule #${params.id}`,
|
||||
isCurrentPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
breadcrumbs.push({
|
||||
label: "Volumes",
|
||||
href: "/volumes",
|
||||
isCurrentPage: pathname === "/volumes",
|
||||
});
|
||||
|
||||
if (pathname.startsWith("/volumes/") && params.name) {
|
||||
breadcrumbs.push({
|
||||
label: params.name,
|
||||
isCurrentPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get breadcrumb data for the current route
|
||||
*/
|
||||
export function useBreadcrumbs(): BreadcrumbItem[] {
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
|
||||
return generateBreadcrumbs(location.pathname, params);
|
||||
}
|
||||
@@ -20,6 +20,13 @@ import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
|
||||
import { SnapshotTimeline } from "../components/snapshot-timeline";
|
||||
import { getBackupSchedule } from "~/client/api-client";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: (match: Route.MetaArgs) => [
|
||||
{ label: "Backups", href: "/backups" },
|
||||
{ label: `Schedule #${match.params.id}` },
|
||||
],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Backup Job Details" },
|
||||
|
||||
@@ -9,6 +9,10 @@ import type { Route } from "./+types/backups";
|
||||
import { listBackupSchedules } from "~/client/api-client";
|
||||
import { listBackupSchedulesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Backups" }],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Backup Jobs" },
|
||||
|
||||
@@ -18,6 +18,10 @@ import { CreateScheduleForm, type BackupScheduleFormValues } from "../components
|
||||
import type { Route } from "./+types/create-backup";
|
||||
import { listRepositories, listVolumes } from "~/client/api-client";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Backups", href: "/backups" }, { label: "Create" }],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Create Backup Job" },
|
||||
|
||||
@@ -15,6 +15,10 @@ import type { Route } from "./+types/repositories";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
import { EmptyState } from "~/client/components/empty-state";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Repositories" }],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Repositories" },
|
||||
|
||||
@@ -27,6 +27,13 @@ import { RepositoryInfoTabContent } from "../tabs/info";
|
||||
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: (match: Route.MetaArgs) => [
|
||||
{ label: "Repositories", href: "/repositories" },
|
||||
{ label: match.params.name },
|
||||
],
|
||||
};
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: params.name },
|
||||
|
||||
@@ -7,6 +7,14 @@ import { SnapshotFileBrowser } from "~/client/modules/backups/components/snapsho
|
||||
import { getSnapshotDetails } from "~/client/api-client";
|
||||
import type { Route } from "./+types/snapshot-details";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: (match: Route.MetaArgs) => [
|
||||
{ label: "Repositories", href: "/repositories" },
|
||||
{ label: match.params.name, href: `/repositories/${match.params.name}` },
|
||||
{ label: match.params.snapshotId },
|
||||
],
|
||||
};
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: `Snapshot ${params.snapshotId}` },
|
||||
|
||||
@@ -24,6 +24,10 @@ import {
|
||||
logoutMutation,
|
||||
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Settings" }],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Settings" },
|
||||
|
||||
@@ -31,6 +31,10 @@ import {
|
||||
unmountVolumeMutation,
|
||||
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: (match: Route.MetaArgs) => [{ label: "Volumes", href: "/volumes" }, { label: match.params.name }],
|
||||
};
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: params.name },
|
||||
|
||||
@@ -15,6 +15,10 @@ import type { Route } from "./+types/volumes";
|
||||
import { listVolumes } from "~/client/api-client";
|
||||
import { listVolumesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Volumes" }],
|
||||
};
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "Volumes" },
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { redirect } from "react-router";
|
||||
|
||||
export const loader = async () => {
|
||||
return redirect("/volumes");
|
||||
};
|
||||
|
||||
export const clientLoader = async () => {
|
||||
return redirect("/volumes");
|
||||
};
|
||||
|
||||
export const loader = async () => {
|
||||
return redirect("/volumes");
|
||||
};
|
||||
|
||||
@@ -39,6 +39,14 @@ class SchedulerClass {
|
||||
this.tasks = [];
|
||||
logger.info("Scheduler stopped");
|
||||
}
|
||||
|
||||
async clear() {
|
||||
for (const task of this.tasks) {
|
||||
task.destroy();
|
||||
}
|
||||
this.tasks = [];
|
||||
logger.info("Scheduler cleared all tasks");
|
||||
}
|
||||
}
|
||||
|
||||
export const Scheduler = new SchedulerClass();
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CleanupSessionsJob } from "../../jobs/cleanup-sessions";
|
||||
|
||||
export const startup = async () => {
|
||||
await Scheduler.start();
|
||||
await Scheduler.clear();
|
||||
|
||||
await restic.ensurePassfile().catch((err) => {
|
||||
logger.error(`Error ensuring restic passfile exists: ${err.message}`);
|
||||
|
||||
Reference in New Issue
Block a user