From 082192f38a3fdd4ed1e5b5a2c6ae536ceaabc479 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sat, 27 Sep 2025 10:51:31 +0200 Subject: [PATCH] refactor: autoRemount as boolean --- apps/client/app/api-client/types.gen.ts | 4 +- apps/client/app/components/onoff.tsx | 25 ++++ apps/client/app/components/ui/switch.tsx | 47 ++++--- .../details/components/healthchecks-card.tsx | 15 +-- .../app/modules/details/tabs/backups.tsx | 18 ++- .../app/modules/details/tabs/docker.tsx | 60 +++++---- apps/server/drizzle/0004_wealthy_tomas.sql | 20 +++ apps/server/drizzle/meta/0004_snapshot.json | 118 ++++++++++++++++++ apps/server/drizzle/meta/_journal.json | 7 ++ apps/server/src/db/schema.ts | 2 +- apps/server/src/modules/volumes/volume.dto.ts | 2 +- .../src/modules/volumes/volume.service.ts | 2 +- 12 files changed, 239 insertions(+), 81 deletions(-) create mode 100644 apps/client/app/components/onoff.tsx create mode 100644 apps/server/drizzle/0004_wealthy_tomas.sql create mode 100644 apps/server/drizzle/meta/0004_snapshot.json diff --git a/apps/client/app/api-client/types.gen.ts b/apps/client/app/api-client/types.gen.ts index f7366ea..eade818 100644 --- a/apps/client/app/api-client/types.gen.ts +++ b/apps/client/app/api-client/types.gen.ts @@ -13,7 +13,7 @@ export type ListVolumesResponses = { */ 200: { volumes: Array<{ - autoRemount: 0 | 1; + autoRemount: boolean; config: | { backend: "directory"; @@ -209,7 +209,7 @@ export type GetVolumeResponses = { used: number; }; volume: { - autoRemount: 0 | 1; + autoRemount: boolean; config: | { backend: "directory"; diff --git a/apps/client/app/components/onoff.tsx b/apps/client/app/components/onoff.tsx new file mode 100644 index 0000000..90089fd --- /dev/null +++ b/apps/client/app/components/onoff.tsx @@ -0,0 +1,25 @@ +import { cn } from "~/lib/utils"; +import { Switch } from "./ui/switch"; + +type Props = { + isOn: boolean; + toggle: (v: boolean) => void; + enabledLabel: string; + disabledLabel: string; +}; + +export const OnOff = ({ isOn, toggle, enabledLabel, disabledLabel }: Props) => { + return ( +
+ {isOn ? enabledLabel : disabledLabel} + +
+ ); +}; diff --git a/apps/client/app/components/ui/switch.tsx b/apps/client/app/components/ui/switch.tsx index a9cecb6..02392c0 100644 --- a/apps/client/app/components/ui/switch.tsx +++ b/apps/client/app/components/ui/switch.tsx @@ -1,29 +1,26 @@ -import * as React from "react" -import * as SwitchPrimitive from "@radix-ui/react-switch" +import * as SwitchPrimitive from "@radix-ui/react-switch"; +import type * as React from "react"; -import { cn } from "~/lib/utils" +import { cn } from "~/lib/utils"; -function Switch({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - ) +function Switch({ className, ...props }: React.ComponentProps) { + return ( + + + + ); } -export { Switch } +export { Switch }; diff --git a/apps/client/app/modules/details/components/healthchecks-card.tsx b/apps/client/app/modules/details/components/healthchecks-card.tsx index 616f4c4..0ef7329 100644 --- a/apps/client/app/modules/details/components/healthchecks-card.tsx +++ b/apps/client/app/modules/details/components/healthchecks-card.tsx @@ -1,10 +1,9 @@ import { formatDistanceToNow } from "date-fns"; import { HeartIcon } from "lucide-react"; +import { OnOff } from "~/components/onoff"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; -import { Switch } from "~/components/ui/switch"; import type { Volume } from "~/lib/types"; -import { cn } from "~/lib/utils"; type Props = { volume: Volume; @@ -34,17 +33,7 @@ export const HealthchecksCard = ({ volume }: Props) => { Remount on error -
- {volume.autoRemount ? "Enabled" : "Paused"} - {}} /> -
+ {}} enabledLabel="Enabled" disabledLabel="Paused" />
{volume.status !== "unmounted" && ( diff --git a/apps/client/app/modules/details/tabs/backups.tsx b/apps/client/app/modules/details/tabs/backups.tsx index 1720b56..8c7c921 100644 --- a/apps/client/app/modules/details/tabs/backups.tsx +++ b/apps/client/app/modules/details/tabs/backups.tsx @@ -1,5 +1,6 @@ import { useMemo } from "react"; import { useForm } from "react-hook-form"; +import { OnOff } from "~/components/onoff"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; @@ -152,17 +153,12 @@ export const VolumeBackupsTabContent = ({ volume }: Props) => { render={({ field }) => ( -
- {field.value ? "Enabled" : "Paused"} - -
+
)} diff --git a/apps/client/app/modules/details/tabs/docker.tsx b/apps/client/app/modules/details/tabs/docker.tsx index 4af2c2a..bfbfc29 100644 --- a/apps/client/app/modules/details/tabs/docker.tsx +++ b/apps/client/app/modules/details/tabs/docker.tsx @@ -5,6 +5,7 @@ import { CodeBlock } from "~/components/ui/code-block"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; import type { Volume } from "~/lib/types"; import { getContainersUsingVolumeOptions } from "../../../api-client/@tanstack/react-query.gen"; +import { Unplug } from "lucide-react"; type Props = { volume: Volume; @@ -78,39 +79,44 @@ export const DockerTabContent = ({ volume }: Props) => { List of Docker containers mounting this volume. - + {isLoading &&
Loading containers...
} {error &&
Failed to load containers: {String(error)}
} {!isLoading && !error && containers.length === 0 && ( -
No containers are currently using this volume.
+
+ +

No Docker containers are currently using this volume.

+
)} {!isLoading && !error && containers.length > 0 && ( - - - - Name - ID - State - Image - - - - {containers.map((container) => ( - - {container.name} - {container.id.slice(0, 12)} - - - {container.state} - - - {container.image} +
+
+ + + Name + ID + State + Image - ))} - -
+ + + {containers.map((container) => ( + + {container.name} + {container.id.slice(0, 12)} + + + {container.state} + + + {container.image} + + ))} + + + )}
diff --git a/apps/server/drizzle/0004_wealthy_tomas.sql b/apps/server/drizzle/0004_wealthy_tomas.sql new file mode 100644 index 0000000..b99a187 --- /dev/null +++ b/apps/server/drizzle/0004_wealthy_tomas.sql @@ -0,0 +1,20 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_volumes_table` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `path` text NOT NULL, + `type` text NOT NULL, + `status` text DEFAULT 'unmounted' NOT NULL, + `last_error` text, + `last_health_check` integer DEFAULT (unixepoch()) NOT NULL, + `created_at` integer DEFAULT (unixepoch()) NOT NULL, + `updated_at` integer DEFAULT (unixepoch()) NOT NULL, + `config` text NOT NULL, + `auto_remount` integer DEFAULT true NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_volumes_table`("id", "name", "path", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount") SELECT "id", "name", "path", "type", "status", "last_error", "last_health_check", "created_at", "updated_at", "config", "auto_remount" FROM `volumes_table`;--> statement-breakpoint +DROP TABLE `volumes_table`;--> statement-breakpoint +ALTER TABLE `__new_volumes_table` RENAME TO `volumes_table`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE UNIQUE INDEX `volumes_table_name_unique` ON `volumes_table` (`name`); \ No newline at end of file diff --git a/apps/server/drizzle/meta/0004_snapshot.json b/apps/server/drizzle/meta/0004_snapshot.json new file mode 100644 index 0000000..4aa90ca --- /dev/null +++ b/apps/server/drizzle/meta/0004_snapshot.json @@ -0,0 +1,118 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "0b087a68-fbc6-4647-a6dc-e6322a3d4ee3", + "prevId": "b7f1ccb8-7bb3-486f-a103-b95b331a121f", + "tables": { + "volumes_table": { + "name": "volumes_table", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unmounted'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_health_check": { + "name": "last_health_check", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auto_remount": { + "name": "auto_remount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "volumes_table_name_unique": { + "name": "volumes_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index 5664a51..ecb66bb 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1758653407064, "tag": "0003_mature_hellcat", "breakpoints": true + }, + { + "idx": 4, + "version": "6", + "when": 1758961535488, + "tag": "0004_wealthy_tomas", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/server/src/db/schema.ts b/apps/server/src/db/schema.ts index 833e92e..8918f18 100644 --- a/apps/server/src/db/schema.ts +++ b/apps/server/src/db/schema.ts @@ -13,7 +13,7 @@ export const volumesTable = sqliteTable("volumes_table", { createdAt: int("created_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`), updatedAt: int("updated_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`), config: text("config", { mode: "json" }).$type().notNull(), - autoRemount: int("auto_remount").$type<1 | 0>().notNull().default(1), + autoRemount: int("auto_remount", { mode: "boolean" }).notNull().default(true), }); export type Volume = typeof volumesTable.$inferSelect; diff --git a/apps/server/src/modules/volumes/volume.dto.ts b/apps/server/src/modules/volumes/volume.dto.ts index bf015a3..7cf29f4 100644 --- a/apps/server/src/modules/volumes/volume.dto.ts +++ b/apps/server/src/modules/volumes/volume.dto.ts @@ -13,7 +13,7 @@ const volumeSchema = type({ updatedAt: "number", lastHealthCheck: "number", config: volumeConfigSchema, - autoRemount: "0 | 1", + autoRemount: "boolean", }); export type VolumeDto = typeof volumeSchema.infer; diff --git a/apps/server/src/modules/volumes/volume.service.ts b/apps/server/src/modules/volumes/volume.service.ts index f9633d6..ea16f53 100644 --- a/apps/server/src/modules/volumes/volume.service.ts +++ b/apps/server/src/modules/volumes/volume.service.ts @@ -155,7 +155,7 @@ const testConnection = async (backendConfig: BackendConfig) => { type: backendConfig.backend, status: "unmounted" as const, lastError: null, - autoRemount: 0 as const, + autoRemount: true, }; const backend = createVolumeBackend(mockVolume);