mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat(client): test mount from form
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||
import { volumeConfigSchema } from "@ironmount/schemas";
|
||||
import { type } from "arktype";
|
||||
import { Plus } from "lucide-react";
|
||||
import { CheckCircle, Loader2, Plus, XCircle } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { testConnection } from "~/api-client";
|
||||
import { slugify } from "~/lib/utils";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
@@ -17,6 +19,9 @@ import {
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form";
|
||||
import { Input } from "./ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { testConnectionMutation } from "~/api-client/@tanstack/react-query.gen";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const formSchema = type({
|
||||
name: "2<=string<=32",
|
||||
@@ -30,6 +35,9 @@ type Props = {
|
||||
};
|
||||
|
||||
export const CreateVolumeDialog = ({ open, setOpen, onSubmit }: Props) => {
|
||||
const [testStatus, setTestStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
|
||||
const [testMessage, setTestMessage] = useState<string>("");
|
||||
|
||||
const form = useForm<typeof formSchema.infer>({
|
||||
resolver: arktypeResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -38,8 +46,36 @@ export const CreateVolumeDialog = ({ open, setOpen, onSubmit }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const testBackendConnection = useMutation({
|
||||
...testConnectionMutation(),
|
||||
onMutate: () => {
|
||||
setTestStatus("loading");
|
||||
},
|
||||
onError: () => {
|
||||
setTestStatus("error");
|
||||
setTestMessage("Failed to test connection. Please try again.");
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data?.success) {
|
||||
setTestStatus("success");
|
||||
setTestMessage(data.message);
|
||||
} else {
|
||||
setTestStatus("error");
|
||||
setTestMessage(data?.message || "Connection test failed");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const watchedBackend = form.watch("backend");
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
const formValues = form.getValues();
|
||||
|
||||
testBackendConnection.mutate({
|
||||
body: { config: formValues },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
@@ -165,11 +201,40 @@ export const CreateVolumeDialog = ({ open, setOpen, onSubmit }: Props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* {createVolume.error && ( */}
|
||||
{/* <div className="text-red-500 text-sm"> */}
|
||||
{/* {createVolume.error.message} */}
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
{watchedBackend === "nfs" && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleTestConnection}
|
||||
disabled={testStatus === "loading" || !form.watch("server") || !form.watch("exportPath")}
|
||||
className="flex-1"
|
||||
>
|
||||
{testStatus === "loading" && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{testStatus === "success" && <CheckCircle className="mr-2 h-4 w-4 text-green-500" />}
|
||||
{testStatus === "error" && <XCircle className="mr-2 h-4 w-4 text-red-500" />}
|
||||
{testStatus === "idle" && "Test Connection"}
|
||||
{testStatus === "loading" && "Testing..."}
|
||||
{testStatus === "success" && "Connection Successful"}
|
||||
{testStatus === "error" && "Test Failed"}
|
||||
</Button>
|
||||
</div>
|
||||
{testMessage && (
|
||||
<div
|
||||
className={`text-sm p-2 rounded-md ${
|
||||
testStatus === "success"
|
||||
? "bg-green-50 text-green-700 border border-green-200"
|
||||
: testStatus === "error"
|
||||
? "bg-red-50 text-red-700 border border-red-200"
|
||||
: "bg-gray-50 text-gray-700 border border-gray-200"
|
||||
}`}
|
||||
>
|
||||
{testMessage}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function Home({ loaderData, actionData }: Route.ComponentProps) {
|
||||
<main className="relative flex flex-col pt-16 p-4 container mx-auto">
|
||||
<h1 className="text-3xl font-bold mb-0 uppercase">Ironmount</h1>
|
||||
<h2 className="text-sm font-semibold mb-2 text-muted-foreground">
|
||||
Create, manage, monitor, and automate your Docker volumes with ease.
|
||||
Create, manage, monitor, and automate your volumes with ease.
|
||||
</h2>
|
||||
<div className="flex items-center gap-2 mt-4 justify-between">
|
||||
<span className="flex items-center gap-2">
|
||||
@@ -134,7 +134,7 @@ export default function Home({ loaderData, actionData }: Route.ComponentProps) {
|
||||
/>
|
||||
</div>
|
||||
<Table className="mt-4 border bg-white dark:bg-secondary">
|
||||
<TableCaption>A list of your managed Docker volumes.</TableCaption>
|
||||
<TableCaption>A list of your managed volumes.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px] uppercase">Name</TableHead>
|
||||
|
||||
@@ -13,11 +13,9 @@ export const generalDescriptor = (app: Hono) =>
|
||||
info: {
|
||||
title: "Ironmount API",
|
||||
version: "1.0.0",
|
||||
description: "API for managing Docker volumes",
|
||||
description: "API for managing volumes",
|
||||
},
|
||||
servers: [
|
||||
{ url: "http://localhost:3000", description: "Development Server" },
|
||||
],
|
||||
servers: [{ url: "http://localhost:3000", description: "Development Server" }],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,9 +56,7 @@ const socketPath = "/run/docker/plugins/ironmount.sock";
|
||||
fetch: app.fetch,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Server is running at http://localhost:8080 and unix socket at ${socketPath}`,
|
||||
);
|
||||
console.log(`Server is running at http://localhost:8080 and unix socket at ${socketPath}`);
|
||||
})();
|
||||
|
||||
export type AppType = typeof app;
|
||||
|
||||
@@ -19,6 +19,7 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
console.log("Mount command executed:", { cmd, error, stdout, stderr });
|
||||
if (error) {
|
||||
console.error(`Error mounting NFS volume: ${stderr}`);
|
||||
return reject(new Error(`Failed to mount NFS volume: ${stderr}`));
|
||||
|
||||
@@ -12,7 +12,7 @@ export const nfsConfigSchema = type({
|
||||
backend: "'nfs'",
|
||||
server: "string",
|
||||
exportPath: "string",
|
||||
port: "number >= 1",
|
||||
port: type("string.integer.parse").or(type("number")).to("1 <= number <= 65536").default("2049"),
|
||||
version: "'3' | '4' | '4.1'",
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user