diff --git a/apps/client/app/api-client/types.gen.ts b/apps/client/app/api-client/types.gen.ts
index 184207f..6e7b018 100644
--- a/apps/client/app/api-client/types.gen.ts
+++ b/apps/client/app/api-client/types.gen.ts
@@ -256,7 +256,8 @@ export type GetVolumeResponse = GetVolumeResponses[keyof GetVolumeResponses];
export type UpdateVolumeData = {
body?: {
- config:
+ autoRemount?: boolean;
+ config?:
| {
backend: "directory";
}
@@ -308,6 +309,7 @@ export type UpdateVolumeResponses = {
200: {
message: string;
volume: {
+ autoRemount: boolean;
config:
| {
backend: "directory";
@@ -339,9 +341,12 @@ export type UpdateVolumeResponses = {
username?: string;
};
createdAt: number;
+ lastError: string;
+ lastHealthCheck: number;
name: string;
path: string;
- type: string;
+ status: "error" | "mounted" | "unknown" | "unmounted";
+ type: "directory" | "nfs" | "smb" | "webdav";
updatedAt: number;
};
};
diff --git a/apps/client/app/components/onoff.tsx b/apps/client/app/components/onoff.tsx
index 90089fd..1ee4a6f 100644
--- a/apps/client/app/components/onoff.tsx
+++ b/apps/client/app/components/onoff.tsx
@@ -6,9 +6,10 @@ type Props = {
toggle: (v: boolean) => void;
enabledLabel: string;
disabledLabel: string;
+ disabled?: boolean;
};
-export const OnOff = ({ isOn, toggle, enabledLabel, disabledLabel }: Props) => {
+export const OnOff = ({ isOn, toggle, enabledLabel, disabledLabel, disabled }: Props) => {
return (
{
)}
>
{isOn ? enabledLabel : disabledLabel}
-
+
);
};
diff --git a/apps/client/app/modules/details/components/healthchecks-card.tsx b/apps/client/app/modules/details/components/healthchecks-card.tsx
index a7cbf36..c76035a 100644
--- a/apps/client/app/modules/details/components/healthchecks-card.tsx
+++ b/apps/client/app/modules/details/components/healthchecks-card.tsx
@@ -2,7 +2,7 @@ import { useMutation } from "@tanstack/react-query";
import { formatDistanceToNow } from "date-fns";
import { HeartIcon } from "lucide-react";
import { toast } from "sonner";
-import { healthCheckVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
+import { healthCheckVolumeMutation, updateVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
import { OnOff } from "~/components/onoff";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
@@ -28,6 +28,15 @@ export const HealthchecksCard = ({ volume }: Props) => {
},
});
+ const toggleAutoRemount = useMutation({
+ ...updateVolumeMutation(),
+ onSuccess: (d) => {
+ toast.success("Volume updated", {
+ description: `Auto remount is now ${d.volume.autoRemount ? "enabled" : "paused"}.`,
+ });
+ },
+ });
+
return (
@@ -46,7 +55,15 @@ export const HealthchecksCard = ({ volume }: Props) => {
)}
Remount on error
- {}} enabledLabel="Enabled" disabledLabel="Paused" />
+
+ toggleAutoRemount.mutate({ path: { name: volume.name }, body: { autoRemount: !volume.autoRemount } })
+ }
+ disabled={toggleAutoRemount.isPending}
+ enabledLabel="Enabled"
+ disabledLabel="Paused"
+ />
{volume.status !== "unmounted" && (
diff --git a/apps/server/src/modules/lifecycle/startup.ts b/apps/server/src/modules/lifecycle/startup.ts
index b6125a2..17d55e4 100644
--- a/apps/server/src/modules/lifecycle/startup.ts
+++ b/apps/server/src/modules/lifecycle/startup.ts
@@ -9,7 +9,7 @@ export const startup = async () => {
const volumes = await db.query.volumesTable.findMany({
where: or(
eq(volumesTable.status, "mounted"),
- and(eq(volumesTable.autoRemount, 1), eq(volumesTable.status, "error")),
+ and(eq(volumesTable.autoRemount, true), eq(volumesTable.status, "error")),
),
});
@@ -28,10 +28,8 @@ export const startup = async () => {
});
for (const volume of volumes) {
- const { error } = await volumeService.checkHealth(volume.name);
- if (error && volume.autoRemount) {
- // TODO: retry with backoff based on last health check time
- // Until we reach the max backoff and it'll try every 10 minutes
+ const { status } = await volumeService.checkHealth(volume.name);
+ if (status === "error" && volume.autoRemount) {
await volumeService.mountVolume(volume.name);
}
}
diff --git a/apps/server/src/modules/volumes/volume.controller.ts b/apps/server/src/modules/volumes/volume.controller.ts
index d1a47eb..4d4d99a 100644
--- a/apps/server/src/modules/volumes/volume.controller.ts
+++ b/apps/server/src/modules/volumes/volume.controller.ts
@@ -17,6 +17,7 @@ import {
updateVolumeBody,
updateVolumeDto,
healthCheckDto,
+ type UpdateVolumeResponseDto,
} from "./volume.dto";
import { volumeService } from "./volume.service";
@@ -86,19 +87,17 @@ export const volumeController = new Hono()
.put("/:name", updateVolumeDto, validator("json", updateVolumeBody), async (c) => {
const { name } = c.req.param();
const body = c.req.valid("json");
- const res = await volumeService.updateVolume(name, body.config);
+ const res = await volumeService.updateVolume(name, body);
const response = {
message: "Volume updated",
volume: {
- name: res.volume.name,
- path: res.volume.path,
- type: res.volume.type,
+ ...res.volume,
createdAt: res.volume.createdAt.getTime(),
updatedAt: res.volume.updatedAt.getTime(),
- config: res.volume.config,
+ lastHealthCheck: res.volume.lastHealthCheck.getTime(),
},
- };
+ } satisfies UpdateVolumeResponseDto;
return c.json(response, 200);
})
diff --git a/apps/server/src/modules/volumes/volume.dto.ts b/apps/server/src/modules/volumes/volume.dto.ts
index 445275b..c139963 100644
--- a/apps/server/src/modules/volumes/volume.dto.ts
+++ b/apps/server/src/modules/volumes/volume.dto.ts
@@ -139,19 +139,15 @@ export const getVolumeDto = describeRoute({
* Update a volume
*/
export const updateVolumeBody = type({
- config: volumeConfigSchema,
+ autoRemount: "boolean?",
+ config: volumeConfigSchema.optional(),
});
+export type UpdateVolumeBody = typeof updateVolumeBody.infer;
+
export const updateVolumeResponse = type({
message: "string",
- volume: type({
- name: "string",
- path: "string",
- type: "string",
- createdAt: "number",
- updatedAt: "number",
- config: volumeConfigSchema,
- }),
+ volume: volumeSchema,
});
export const updateVolumeDto = describeRoute({
@@ -174,6 +170,8 @@ export const updateVolumeDto = describeRoute({
},
});
+export type UpdateVolumeResponseDto = typeof updateVolumeResponse.infer;
+
/**
* Test connection
*/
diff --git a/apps/server/src/modules/volumes/volume.service.ts b/apps/server/src/modules/volumes/volume.service.ts
index ea16f53..96a5dbc 100644
--- a/apps/server/src/modules/volumes/volume.service.ts
+++ b/apps/server/src/modules/volumes/volume.service.ts
@@ -13,6 +13,7 @@ import { volumesTable } from "../../db/schema";
import { toMessage } from "../../utils/errors";
import { getStatFs, type StatFs } from "../../utils/mountinfo";
import { createVolumeBackend } from "../backends/backend";
+import type { UpdateVolumeBody } from "./volume.dto";
const listVolumes = async () => {
const volumes = await db.query.volumesTable.findMany({});
@@ -114,7 +115,7 @@ const getVolume = async (name: string) => {
return { volume, statfs };
};
-const updateVolume = async (name: string, backendConfig: BackendConfig) => {
+const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
const existing = await db.query.volumesTable.findFirst({
where: eq(volumesTable.name, name),
});
@@ -126,10 +127,10 @@ const updateVolume = async (name: string, backendConfig: BackendConfig) => {
const [updated] = await db
.update(volumesTable)
.set({
- config: backendConfig,
- type: backendConfig.backend,
+ config: volumeData.config,
+ type: volumeData.config?.backend,
+ autoRemount: volumeData.autoRemount,
updatedAt: new Date(),
- status: "unmounted",
})
.where(eq(volumesTable.name, name))
.returning();