diff --git a/apps/client/app/app.css b/apps/client/app/app.css
index 3901f0d..c12eee5 100644
--- a/apps/client/app/app.css
+++ b/apps/client/app/app.css
@@ -5,171 +5,171 @@
@custom-variant dark (&:is(.dark *));
@theme {
- --breakpoint-xs: 32rem;
- --font-sans:
- "Google Sans Code", ui-sans-serif, system-ui, sans-serif,
- "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --breakpoint-xs: 32rem;
+ --font-sans:
+ "Google Sans Code", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
+ "Noto Color Emoji";
}
html,
body {
- overflow-x: hidden;
- width: 100%;
- position: relative;
- overscroll-behavior: none;
- scrollbar-width: thin;
+ overflow-x: hidden;
+ width: 100%;
+ position: relative;
+ overscroll-behavior: none;
+ scrollbar-width: thin;
}
body {
- @apply bg-[#131313];
- min-height: 100dvh;
+ @apply bg-[#131313];
+ min-height: 100dvh;
}
.main-content {
- scrollbar-width: thin;
- scrollbar-gutter: stable;
+ scrollbar-width: thin;
+ scrollbar-gutter: stable;
}
@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-card-header: var(--card-header);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
- --color-strong-accent: var(--strong-accent);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-card-header: var(--card-header);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-strong-accent: var(--strong-accent);
}
:root {
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.145 0 0);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.145 0 0);
- --card-header: oklch(0.922 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
- --primary-foreground: oklch(0.985 0 0);
- --secondary: oklch(0.97 0 0);
- --secondary-foreground: oklch(0.205 0 0);
- --muted: oklch(0.97 0 0);
- --muted-foreground: oklch(0.556 0 0);
- --accent: oklch(0.97 0 0);
- --accent-foreground: oklch(0.205 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
- --strong-accent: #ff543a;
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --card-header: oklch(0.922 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+ --strong-accent: #ff543a;
}
.dark {
- color-scheme: dark;
+ color-scheme: dark;
- --background: #131313;
- --foreground: oklch(0.985 0 0);
- --card: #131313;
- --card-header: #1b1b1b;
- /* --card: oklch(0.205 0 0); ORIGINAL */
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.205 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.922 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.1448 0 0);
- /* --secondary: oklch(0.269 0 0); ORIGINAL */
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: #ff543a;
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.556 0 0);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0);
- --strong-accent: #ff543a;
+ --background: #131313;
+ --foreground: oklch(0.985 0 0);
+ --card: #131313;
+ --card-header: #1b1b1b;
+ /* --card: oklch(0.205 0 0); ORIGINAL */
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.1448 0 0);
+ /* --secondary: oklch(0.269 0 0); ORIGINAL */
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: #ff543a;
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+ --strong-accent: #ff543a;
}
@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
}
@layer base {
- :root {
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- }
+ :root {
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ }
- .dark {
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- }
+ .dark {
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ }
}
diff --git a/apps/client/app/components/auth-layout.tsx b/apps/client/app/components/auth-layout.tsx
index eb8b668..2435a41 100644
--- a/apps/client/app/components/auth-layout.tsx
+++ b/apps/client/app/components/auth-layout.tsx
@@ -2,33 +2,33 @@ import { Mountain } from "lucide-react";
import type { ReactNode } from "react";
type AuthLayoutProps = {
- title: string;
- description: string;
- children: ReactNode;
+ title: string;
+ description: string;
+ children: ReactNode;
};
export function AuthLayout({ title, description, children }: AuthLayoutProps) {
- return (
-
-
-
-
-
- Ironmount
-
+ return (
+
+
+
+
+
+ Ironmount
+
-
-
{title}
-
{description}
-
+
+
{title}
+
{description}
+
- {children}
-
-
-
-
- );
+ {children}
+
+
+
+
+ );
}
diff --git a/apps/client/app/components/layout.tsx b/apps/client/app/components/layout.tsx
index ca5cc29..70caebb 100644
--- a/apps/client/app/components/layout.tsx
+++ b/apps/client/app/components/layout.tsx
@@ -15,84 +15,73 @@ import { AppSidebar } from "./app-sidebar";
export const clientMiddleware = [authMiddleware];
export async function clientLoader({ context }: Route.LoaderArgs) {
- const ctx = context.get(appContext);
+ const ctx = context.get(appContext);
- if (ctx.user && !ctx.user.hasDownloadedResticPassword) {
- throw redirect("/download-recovery-key");
- }
+ if (ctx.user && !ctx.user.hasDownloadedResticPassword) {
+ throw redirect("/download-recovery-key");
+ }
- return ctx;
+ return ctx;
}
export default function Layout({ loaderData }: Route.ComponentProps) {
- const navigate = useNavigate();
+ const navigate = useNavigate();
- const logout = useMutation({
- ...logoutMutation(),
- onSuccess: async () => {
- navigate("/login", { replace: true });
- },
- onError: (error) => {
- console.error(error);
- toast.error("Logout failed", { description: error.message });
- },
- });
+ const logout = useMutation({
+ ...logoutMutation(),
+ onSuccess: async () => {
+ navigate("/login", { replace: true });
+ },
+ onError: (error) => {
+ console.error(error);
+ toast.error("Logout failed", { description: error.message });
+ },
+ });
- return (
-
-
-
-
- );
+ return (
+
+
+
+
+ );
}
diff --git a/apps/client/app/components/onoff.tsx b/apps/client/app/components/onoff.tsx
index d4100e2..a5b690d 100644
--- a/apps/client/app/components/onoff.tsx
+++ b/apps/client/app/components/onoff.tsx
@@ -2,36 +2,30 @@ import { cn } from "~/lib/utils";
import { Switch } from "./ui/switch";
type Props = {
- isOn: boolean;
- toggle: (v: boolean) => void;
- enabledLabel: string;
- disabledLabel: string;
- disabled?: boolean;
+ isOn: boolean;
+ toggle: (v: boolean) => void;
+ enabledLabel: string;
+ disabledLabel: string;
+ disabled?: boolean;
};
-export const OnOff = ({
- isOn,
- toggle,
- enabledLabel,
- disabledLabel,
- disabled,
-}: Props) => {
- return (
-
- {isOn ? enabledLabel : disabledLabel}
-
-
- );
+export const OnOff = ({ isOn, toggle, enabledLabel, disabledLabel, disabled }: Props) => {
+ return (
+
+ {isOn ? enabledLabel : disabledLabel}
+
+
+ );
};
diff --git a/apps/client/app/components/status-dot.tsx b/apps/client/app/components/status-dot.tsx
index 37c0439..9206a33 100644
--- a/apps/client/app/components/status-dot.tsx
+++ b/apps/client/app/components/status-dot.tsx
@@ -3,53 +3,50 @@ import { cn } from "~/lib/utils";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
export const StatusDot = ({ status }: { status: VolumeStatus }) => {
- const statusMapping = {
- mounted: {
- color: "bg-green-500",
- colorLight: "bg-emerald-400",
- animated: true,
- },
- unmounted: {
- color: "bg-gray-500",
- colorLight: "bg-gray-400",
- animated: false,
- },
- error: {
- color: "bg-red-500",
- colorLight: "bg-amber-700",
- animated: true,
- },
- unknown: {
- color: "bg-yellow-500",
- colorLight: "bg-yellow-400",
- animated: true,
- },
- }[status];
+ const statusMapping = {
+ mounted: {
+ color: "bg-green-500",
+ colorLight: "bg-emerald-400",
+ animated: true,
+ },
+ unmounted: {
+ color: "bg-gray-500",
+ colorLight: "bg-gray-400",
+ animated: false,
+ },
+ error: {
+ color: "bg-red-500",
+ colorLight: "bg-amber-700",
+ animated: true,
+ },
+ unknown: {
+ color: "bg-yellow-500",
+ colorLight: "bg-yellow-400",
+ animated: true,
+ },
+ }[status];
- return (
-
-
-
- {statusMapping.animated && (
-
- )}
-
-
-
-
- {status}
-
-
- );
+ return (
+
+
+
+ {statusMapping.animated && (
+
+ )}
+
+
+
+
+ {status}
+
+
+ );
};
diff --git a/apps/client/app/modules/repositories/routes/repository-details.tsx b/apps/client/app/modules/repositories/routes/repository-details.tsx
index e00ab92..dc15d17 100644
--- a/apps/client/app/modules/repositories/routes/repository-details.tsx
+++ b/apps/client/app/modules/repositories/routes/repository-details.tsx
@@ -3,19 +3,19 @@ import { useNavigate, useParams, useSearchParams } from "react-router";
import { toast } from "sonner";
import { useState, useEffect } from "react";
import {
- deleteRepositoryMutation,
- getRepositoryOptions,
- listSnapshotsOptions,
+ deleteRepositoryMutation,
+ getRepositoryOptions,
+ listSnapshotsOptions,
} from "~/api-client/@tanstack/react-query.gen";
import { Button } from "~/components/ui/button";
import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogHeader,
- AlertDialogTitle,
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogHeader,
+ AlertDialogTitle,
} from "~/components/ui/alert-dialog";
import { parseError } from "~/lib/errors";
import { getRepository } from "~/api-client/sdk.gen";
@@ -26,137 +26,122 @@ import { RepositoryInfoTabContent } from "../tabs/info";
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
export function meta({ params }: Route.MetaArgs) {
- return [
- { title: params.name },
- {
- name: "description",
- content: "View repository configuration, status, and snapshots.",
- },
- ];
+ return [
+ { title: params.name },
+ {
+ name: "description",
+ content: "View repository configuration, status, and snapshots.",
+ },
+ ];
}
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
- const repository = await getRepository({ path: { name: params.name ?? "" } });
- if (repository.data) return repository.data;
+ const repository = await getRepository({ path: { name: params.name ?? "" } });
+ if (repository.data) return repository.data;
};
-export default function RepositoryDetailsPage({
- loaderData,
-}: Route.ComponentProps) {
- const { name } = useParams<{ name: string }>();
- const navigate = useNavigate();
- const queryClient = useQueryClient();
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+export default function RepositoryDetailsPage({ loaderData }: Route.ComponentProps) {
+ const { name } = useParams<{ name: string }>();
+ const navigate = useNavigate();
+ const queryClient = useQueryClient();
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
- const [searchParams, setSearchParams] = useSearchParams();
- const activeTab = searchParams.get("tab") || "info";
+ const [searchParams, setSearchParams] = useSearchParams();
+ const activeTab = searchParams.get("tab") || "info";
- const { data } = useQuery({
- ...getRepositoryOptions({ path: { name: name ?? "" } }),
- initialData: loaderData,
- refetchInterval: 10000,
- refetchOnWindowFocus: true,
- });
+ const { data } = useQuery({
+ ...getRepositoryOptions({ path: { name: name ?? "" } }),
+ initialData: loaderData,
+ refetchInterval: 10000,
+ refetchOnWindowFocus: true,
+ });
- useEffect(() => {
- if (name) {
- queryClient.prefetchQuery(listSnapshotsOptions({ path: { name } }));
- }
- }, [name, queryClient]);
+ useEffect(() => {
+ if (name) {
+ queryClient.prefetchQuery(listSnapshotsOptions({ path: { name } }));
+ }
+ }, [name, queryClient]);
- const deleteRepo = useMutation({
- ...deleteRepositoryMutation(),
- onSuccess: () => {
- toast.success("Repository deleted successfully");
- navigate("/repositories");
- },
- onError: (error) => {
- toast.error("Failed to delete repository", {
- description: parseError(error)?.message,
- });
- },
- });
+ const deleteRepo = useMutation({
+ ...deleteRepositoryMutation(),
+ onSuccess: () => {
+ toast.success("Repository deleted successfully");
+ navigate("/repositories");
+ },
+ onError: (error) => {
+ toast.error("Failed to delete repository", {
+ description: parseError(error)?.message,
+ });
+ },
+ });
- const handleConfirmDelete = () => {
- setShowDeleteConfirm(false);
- deleteRepo.mutate({ path: { name: name ?? "" } });
- };
+ const handleConfirmDelete = () => {
+ setShowDeleteConfirm(false);
+ deleteRepo.mutate({ path: { name: name ?? "" } });
+ };
- if (!name) {
- return Repository not found
;
- }
+ if (!name) {
+ return Repository not found
;
+ }
- if (!data) {
- return Loading...
;
- }
+ if (!data) {
+ return Loading...
;
+ }
- return (
- <>
-
-
-
- {data.status || "unknown"}
-
-
- {data.type}
-
-
-
- setShowDeleteConfirm(true)}
- disabled={deleteRepo.isPending}
- >
- Delete
-
-
-
+ return (
+ <>
+
+
+
+ {data.status || "unknown"}
+
+ {data.type}
+
+
+ setShowDeleteConfirm(true)} disabled={deleteRepo.isPending}>
+ Delete
+
+
+
- setSearchParams({ tab: value })}
- >
-
- Configuration
- Snapshots
-
-
-
-
-
-
-
-
+ setSearchParams({ tab: value })}>
+
+ Configuration
+ Snapshots
+
+
+
+
+
+
+
+
-
-
-
- Delete repository?
-
- Are you sure you want to delete the repository{" "}
- {name} ? This action cannot be undone and will
- remove all backup data.
-
-
-
-
Cancel
-
- Delete repository
-
-
-
-
- >
- );
+
+
+
+ Delete repository?
+
+ Are you sure you want to delete the repository {name} ? This action cannot be undone and
+ will remove all backup data.
+
+
+
+
Cancel
+
+ Delete repository
+
+
+
+
+ >
+ );
}
diff --git a/apps/client/app/modules/repositories/routes/snapshot-details.tsx b/apps/client/app/modules/repositories/routes/snapshot-details.tsx
index 3776768..f2255ec 100644
--- a/apps/client/app/modules/repositories/routes/snapshot-details.tsx
+++ b/apps/client/app/modules/repositories/routes/snapshot-details.tsx
@@ -8,102 +8,95 @@ import { getSnapshotDetails } from "~/api-client";
import type { Route } from "./+types/snapshot-details";
export function meta({ params }: Route.MetaArgs) {
- return [
- { title: `Snapshot ${params.snapshotId}` },
- {
- name: "description",
- content: "Browse and restore files from a backup snapshot.",
- },
- ];
+ return [
+ { title: `Snapshot ${params.snapshotId}` },
+ {
+ name: "description",
+ content: "Browse and restore files from a backup snapshot.",
+ },
+ ];
}
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
- const snapshot = await getSnapshotDetails({
- path: { name: params.name, snapshotId: params.snapshotId },
- });
- if (snapshot.data) return snapshot.data;
+ const snapshot = await getSnapshotDetails({
+ path: { name: params.name, snapshotId: params.snapshotId },
+ });
+ if (snapshot.data) return snapshot.data;
- return redirect("/repositories");
+ return redirect("/repositories");
};
-export default function SnapshotDetailsPage({
- loaderData,
-}: Route.ComponentProps) {
- const { name, snapshotId } = useParams<{
- name: string;
- snapshotId: string;
- }>();
+export default function SnapshotDetailsPage({ loaderData }: Route.ComponentProps) {
+ const { name, snapshotId } = useParams<{
+ name: string;
+ snapshotId: string;
+ }>();
- const { data } = useQuery({
- ...listSnapshotFilesOptions({
- path: { name: name ?? "", snapshotId: snapshotId ?? "" },
- query: { path: "/" },
- }),
- enabled: !!name && !!snapshotId,
- });
+ const { data } = useQuery({
+ ...listSnapshotFilesOptions({
+ path: { name: name ?? "", snapshotId: snapshotId ?? "" },
+ query: { path: "/" },
+ }),
+ enabled: !!name && !!snapshotId,
+ });
- if (!name || !snapshotId) {
- return (
-
-
Invalid snapshot reference
-
- );
- }
+ if (!name || !snapshotId) {
+ return (
+
+
Invalid snapshot reference
+
+ );
+ }
- return (
-
-
-
-
{name}
-
- Snapshot: {snapshotId}
-
-
-
-
+ return (
+
+
+
+
{name}
+
Snapshot: {snapshotId}
+
+
+
-
+
- {data?.snapshot && (
-
-
- Snapshot Information
-
-
-
-
-
Snapshot ID:
-
{data.snapshot.id}
-
-
-
Short ID:
-
{data.snapshot.short_id}
-
-
-
Hostname:
-
{data.snapshot.hostname}
-
-
-
Time:
-
{new Date(data.snapshot.time).toLocaleString()}
-
-
-
Paths:
-
- {data.snapshot.paths.map((path) => (
-
- {path}
-
- ))}
-
-
-
-
-
- )}
-
- );
+ {data?.snapshot && (
+
+
+ Snapshot Information
+
+
+
+
+
Snapshot ID:
+
{data.snapshot.id}
+
+
+
Short ID:
+
{data.snapshot.short_id}
+
+
+
Hostname:
+
{data.snapshot.hostname}
+
+
+
Time:
+
{new Date(data.snapshot.time).toLocaleString()}
+
+
+
Paths:
+
+ {data.snapshot.paths.map((path) => (
+
+ {path}
+
+ ))}
+
+
+
+
+
+ )}
+
+ );
}
diff --git a/apps/client/app/modules/volumes/routes/volume-details.tsx b/apps/client/app/modules/volumes/routes/volume-details.tsx
index cc19a3e..7ac12a7 100644
--- a/apps/client/app/modules/volumes/routes/volume-details.tsx
+++ b/apps/client/app/modules/volumes/routes/volume-details.tsx
@@ -3,23 +3,23 @@ import { useNavigate, useParams, useSearchParams } from "react-router";
import { toast } from "sonner";
import { useState } from "react";
import {
- deleteVolumeMutation,
- getVolumeOptions,
- getSystemInfoOptions,
- mountVolumeMutation,
- unmountVolumeMutation,
+ deleteVolumeMutation,
+ getVolumeOptions,
+ getSystemInfoOptions,
+ mountVolumeMutation,
+ unmountVolumeMutation,
} from "~/api-client/@tanstack/react-query.gen";
import { StatusDot } from "~/components/status-dot";
import { Button } from "~/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogHeader,
- AlertDialogTitle,
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogHeader,
+ AlertDialogTitle,
} from "~/components/ui/alert-dialog";
import { VolumeIcon } from "~/components/volume-icon";
import { parseError } from "~/lib/errors";
@@ -31,169 +31,159 @@ import { FilesTabContent } from "../tabs/files";
import { DockerTabContent } from "../tabs/docker";
export function meta({ params }: Route.MetaArgs) {
- return [
- { title: params.name },
- {
- name: "description",
- content: "View and manage volume details, configuration, and files.",
- },
- ];
+ return [
+ { title: params.name },
+ {
+ name: "description",
+ content: "View and manage volume details, configuration, and files.",
+ },
+ ];
}
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
- const volume = await getVolume({ path: { name: params.name ?? "" } });
- if (volume.data) return volume.data;
+ const volume = await getVolume({ path: { name: params.name ?? "" } });
+ if (volume.data) return volume.data;
};
export default function VolumeDetails({ loaderData }: Route.ComponentProps) {
- const { name } = useParams<{ name: string }>();
- const navigate = useNavigate();
- const [searchParams, setSearchParams] = useSearchParams();
- const activeTab = searchParams.get("tab") || "info";
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const { name } = useParams<{ name: string }>();
+ const navigate = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const activeTab = searchParams.get("tab") || "info";
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
- const { data } = useQuery({
- ...getVolumeOptions({ path: { name: name ?? "" } }),
- initialData: loaderData,
- refetchInterval: 10000,
- refetchOnWindowFocus: true,
- });
+ const { data } = useQuery({
+ ...getVolumeOptions({ path: { name: name ?? "" } }),
+ initialData: loaderData,
+ refetchInterval: 10000,
+ refetchOnWindowFocus: true,
+ });
- const { data: systemInfo } = useQuery({
- ...getSystemInfoOptions(),
- });
+ const { data: systemInfo } = useQuery({
+ ...getSystemInfoOptions(),
+ });
- const deleteVol = useMutation({
- ...deleteVolumeMutation(),
- onSuccess: () => {
- toast.success("Volume deleted successfully");
- navigate("/volumes");
- },
- onError: (error) => {
- toast.error("Failed to delete volume", {
- description: parseError(error)?.message,
- });
- },
- });
+ const deleteVol = useMutation({
+ ...deleteVolumeMutation(),
+ onSuccess: () => {
+ toast.success("Volume deleted successfully");
+ navigate("/volumes");
+ },
+ onError: (error) => {
+ toast.error("Failed to delete volume", {
+ description: parseError(error)?.message,
+ });
+ },
+ });
- const mountVol = useMutation({
- ...mountVolumeMutation(),
- onSuccess: () => {
- toast.success("Volume mounted successfully");
- },
- onError: (error) => {
- toast.error("Failed to mount volume", {
- description: parseError(error)?.message,
- });
- },
- });
+ const mountVol = useMutation({
+ ...mountVolumeMutation(),
+ onSuccess: () => {
+ toast.success("Volume mounted successfully");
+ },
+ onError: (error) => {
+ toast.error("Failed to mount volume", {
+ description: parseError(error)?.message,
+ });
+ },
+ });
- const unmountVol = useMutation({
- ...unmountVolumeMutation(),
- onSuccess: () => {
- toast.success("Volume unmounted successfully");
- },
- onError: (error) => {
- toast.error("Failed to unmount volume", {
- description: parseError(error)?.message,
- });
- },
- });
+ const unmountVol = useMutation({
+ ...unmountVolumeMutation(),
+ onSuccess: () => {
+ toast.success("Volume unmounted successfully");
+ },
+ onError: (error) => {
+ toast.error("Failed to unmount volume", {
+ description: parseError(error)?.message,
+ });
+ },
+ });
- const handleConfirmDelete = () => {
- setShowDeleteConfirm(false);
- deleteVol.mutate({ path: { name: name ?? "" } });
- };
+ const handleConfirmDelete = () => {
+ setShowDeleteConfirm(false);
+ deleteVol.mutate({ path: { name: name ?? "" } });
+ };
- if (!name) {
- return Volume not found
;
- }
+ if (!name) {
+ return Volume not found
;
+ }
- if (!data) {
- return Loading...
;
- }
+ if (!data) {
+ return Loading...
;
+ }
- const { volume, statfs } = data;
- const dockerAvailable = systemInfo?.capabilities?.docker ?? false;
+ const { volume, statfs } = data;
+ const dockerAvailable = systemInfo?.capabilities?.docker ?? false;
- return (
- <>
-
-
-
- {" "}
- {volume.status[0].toUpperCase() + volume.status.slice(1)}
-
-
-
-
- mountVol.mutate({ path: { name } })}
- loading={mountVol.isPending}
- className={cn({ hidden: volume.status === "mounted" })}
- >
- Mount
-
- unmountVol.mutate({ path: { name } })}
- loading={unmountVol.isPending}
- className={cn({ hidden: volume.status !== "mounted" })}
- >
- Unmount
-
- setShowDeleteConfirm(true)}
- disabled={deleteVol.isPending}
- >
- Delete
-
-
-
- setSearchParams({ tab: value })}
- className="mt-4"
- >
-
- Configuration
- Files
- {dockerAvailable && Docker }
-
-
-
-
-
-
-
- {dockerAvailable && (
-
-
-
- )}
-
+ return (
+ <>
+
+
+
+ {volume.status[0].toUpperCase() + volume.status.slice(1)}
+
+
+
+
+ mountVol.mutate({ path: { name } })}
+ loading={mountVol.isPending}
+ className={cn({ hidden: volume.status === "mounted" })}
+ >
+ Mount
+
+ unmountVol.mutate({ path: { name } })}
+ loading={unmountVol.isPending}
+ className={cn({ hidden: volume.status !== "mounted" })}
+ >
+ Unmount
+
+ setShowDeleteConfirm(true)} disabled={deleteVol.isPending}>
+ Delete
+
+
+
+ setSearchParams({ tab: value })} className="mt-4">
+
+ Configuration
+ Files
+ {dockerAvailable && Docker }
+
+
+
+
+
+
+
+ {dockerAvailable && (
+
+
+
+ )}
+
-
-
-
- Delete volume?
-
- Are you sure you want to delete the volume {name}
- ? This action cannot be undone.
-
-
-
-
Cancel
-
- Delete volume
-
-
-
-
- >
- );
+
+
+
+ Delete volume?
+
+ Are you sure you want to delete the volume {name} ? This action cannot be undone.
+
+
+
+
Cancel
+
+ Delete volume
+
+
+
+
+ >
+ );
}
diff --git a/apps/client/app/root.tsx b/apps/client/app/root.tsx
index 2058fc0..93c8133 100644
--- a/apps/client/app/root.tsx
+++ b/apps/client/app/root.tsx
@@ -1,16 +1,5 @@
-import {
- MutationCache,
- QueryClient,
- QueryClientProvider,
-} from "@tanstack/react-query";
-import {
- isRouteErrorResponse,
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "react-router";
+import { MutationCache, QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import { Toaster } from "~/components/ui/sonner";
import type { Route } from "./+types/root";
@@ -19,108 +8,89 @@ import { client } from "./api-client/client.gen";
import { useServerEvents } from "./hooks/use-server-events";
client.setConfig({
- baseUrl: "/",
+ baseUrl: "/",
});
export const links: Route.LinksFunction = () => [
- { rel: "preconnect", href: "https://fonts.googleapis.com" },
- {
- rel: "preconnect",
- href: "https://fonts.gstatic.com",
- crossOrigin: "anonymous",
- },
- {
- rel: "stylesheet",
- href: "https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&display=swap",
- },
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
+ {
+ rel: "preconnect",
+ href: "https://fonts.gstatic.com",
+ crossOrigin: "anonymous",
+ },
+ {
+ rel: "stylesheet",
+ href: "https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&display=swap",
+ },
];
const queryClient = new QueryClient({
- mutationCache: new MutationCache({
- onSuccess: () => {
- queryClient.invalidateQueries();
- },
- onError: (error) => {
- console.error("Mutation error:", error);
- queryClient.invalidateQueries();
- },
- }),
+ mutationCache: new MutationCache({
+ onSuccess: () => {
+ queryClient.invalidateQueries();
+ },
+ onError: (error) => {
+ console.error("Mutation error:", error);
+ queryClient.invalidateQueries();
+ },
+ }),
});
export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ );
}
export default function App() {
- useServerEvents();
+ useServerEvents();
- return ;
+ return ;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- let message = "Oops!";
- let details = "An unexpected error occurred.";
- let stack: string | undefined;
+ let message = "Oops!";
+ let details = "An unexpected error occurred.";
+ let stack: string | undefined;
- if (isRouteErrorResponse(error)) {
- message = error.status === 404 ? "404" : "Error";
- details =
- error.status === 404
- ? "The requested page could not be found."
- : error.statusText || details;
- } else if (import.meta.env.DEV && error && error instanceof Error) {
- details = error.message;
- stack = error.stack;
- }
+ if (isRouteErrorResponse(error)) {
+ message = error.status === 404 ? "404" : "Error";
+ details = error.status === 404 ? "The requested page could not be found." : error.statusText || details;
+ } else if (import.meta.env.DEV && error && error instanceof Error) {
+ details = error.message;
+ stack = error.stack;
+ }
- return (
-
- {message}
- {details}
- {stack && (
-
- {stack}
-
- )}
-
- );
+ return (
+
+ {message}
+ {details}
+ {stack && (
+
+ {stack}
+
+ )}
+
+ );
}
diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts
index c4096c6..9235c1f 100644
--- a/apps/client/vite.config.ts
+++ b/apps/client/vite.config.ts
@@ -7,28 +7,28 @@ const alias = {};
const { NODE_ENV } = process.env;
if (NODE_ENV === "production") {
- // @ts-expect-error
- alias["react-dom/server"] = "react-dom/server.node";
+ // @ts-expect-error
+ alias["react-dom/server"] = "react-dom/server.node";
}
export default defineConfig({
- plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
- resolve: {
- alias,
- },
- build: {
- outDir: "dist",
- // sourcemap: true,
- },
- server: {
- host: true,
- port: 4097,
- proxy: {
- "/api": {
- target: "http://localhost:4096",
- changeOrigin: true,
- },
- },
- allowedHosts: true,
- },
+ plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
+ resolve: {
+ alias,
+ },
+ build: {
+ outDir: "dist",
+ // sourcemap: true,
+ },
+ server: {
+ host: true,
+ port: 4097,
+ proxy: {
+ "/api": {
+ target: "http://localhost:4096",
+ changeOrigin: true,
+ },
+ },
+ allowedHosts: true,
+ },
});