zerobyte/app/client/modules/volumes/components/healthchecks-card.tsx
Nico 451aed8983
Multi users (#381)
* feat(db): add support for multiple users and organizations

* feat: backfill entities with new organization id

* refactor: filter all backend queries to surface only organization specific entities

* refactor: each org has its own restic password

* test: ensure organization is created

* chore: pr feedbacks

* refactor: filter by org id in all places

* refactor: download restic password from stored db password

* refactor(navigation): use volume id in urls instead of name

* feat: disable registrations

* refactor(auth): bubble up auth error to hono

* refactor: use async local storage for cleaner context sharing

* refactor: enable user registration vs disabling it

* test: multi-org isolation

* chore: final cleanup
2026-01-20 22:28:22 +01:00

87 lines
2.9 KiB
TypeScript

import { useMutation } from "@tanstack/react-query";
import { Activity, HeartIcon } from "lucide-react";
import { toast } from "sonner";
import { healthCheckVolumeMutation, updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { OnOff } from "~/client/components/onoff";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
import { formatTimeAgo } from "~/client/lib/datetime";
import type { Volume } from "~/client/lib/types";
type Props = {
volume: Volume;
};
export const HealthchecksCard = ({ volume }: Props) => {
const healthcheck = useMutation({
...healthCheckVolumeMutation(),
onSuccess: (d) => {
if (d.error) {
toast.error("Health check failed", { description: d.error });
return;
}
toast.success("Health check completed", { description: "The volume is healthy." });
},
onError: (error) => {
toast.error("Health check failed", { description: error.message });
},
});
const toggleAutoRemount = useMutation({
...updateVolumeMutation(),
onSuccess: (d) => {
toast.success("Volume updated", {
description: `Auto remount is now ${d.autoRemount ? "enabled" : "paused"}.`,
});
},
onError: (error) => {
toast.error("Update failed", { description: error.message });
},
});
return (
<Card className="flex-1 flex flex-col h-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<HeartIcon className="h-4 w-4" />
Health Checks
</CardTitle>
<CardDescription>Monitor and automatically remount volumes on errors to ensure availability.</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col flex-1 justify-start">
{volume.lastError && <span className="text-sm text-red-500 wrap-break-word">{volume.lastError}</span>}
{volume.status === "mounted" && <span className="text-md text-green-500">Healthy</span>}
{volume.status !== "unmounted" && (
<span className="text-xs text-muted-foreground mb-4">Checked {formatTimeAgo(volume.lastHealthCheck)}</span>
)}
<span className="flex justify-between items-center gap-2">
<span className="text-sm">Remount on error</span>
<OnOff
isOn={volume.autoRemount}
toggle={() =>
toggleAutoRemount.mutate({ path: { id: volume.shortId }, body: { autoRemount: !volume.autoRemount } })
}
disabled={toggleAutoRemount.isPending}
enabledLabel="Enabled"
disabledLabel="Paused"
/>
</span>
</div>
{volume.status !== "unmounted" && (
<div className="flex justify-center">
<Button
variant="outline"
className="mt-4"
loading={healthcheck.isPending}
onClick={() => healthcheck.mutate({ path: { id: volume.shortId } })}
>
<Activity className="h-4 w-4 mr-2" />
Run Health Check
</Button>
</div>
)}
</CardContent>
</Card>
);
};