Browse Source

on delete cascade for newts to fix delete site

Milo Schwartz 6 months ago
parent
commit
fb4d27085d

+ 3 - 1
server/db/schema.ts

@@ -96,7 +96,9 @@ export const newts = sqliteTable("newt", {
     newtId: text("id").primaryKey(),
     newtId: text("id").primaryKey(),
     secretHash: text("secretHash").notNull(),
     secretHash: text("secretHash").notNull(),
     dateCreated: text("dateCreated").notNull(),
     dateCreated: text("dateCreated").notNull(),
-    siteId: integer("siteId").references(() => sites.siteId)
+    siteId: integer("siteId").references(() => sites.siteId, {
+        onDelete: "cascade"
+    })
 });
 });
 
 
 export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
 export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {

+ 122 - 187
src/app/[orgId]/settings/sites/components/CreateSiteForm.tsx

@@ -1,6 +1,5 @@
 "use client";
 "use client";
 
 
-import { Button, buttonVariants } from "@app/components/ui/button";
 import {
 import {
     Form,
     Form,
     FormControl,
     FormControl,
@@ -16,17 +15,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
 import { useEffect, useState } from "react";
 import { useEffect, useState } from "react";
 import { useForm } from "react-hook-form";
 import { useForm } from "react-hook-form";
 import { z } from "zod";
 import { z } from "zod";
-import {
-    Credenza,
-    CredenzaBody,
-    CredenzaClose,
-    CredenzaContent,
-    CredenzaDescription,
-    CredenzaFooter,
-    CredenzaHeader,
-    CredenzaTitle
-} from "@app/components/Credenza";
-import { useOrgContext } from "@app/hooks/useOrgContext";
 import { useParams, useRouter } from "next/navigation";
 import { useParams, useRouter } from "next/navigation";
 import {
 import {
     CreateSiteBody,
     CreateSiteBody,
@@ -49,11 +37,6 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
 import { SiteRow } from "./SitesTable";
 import { SiteRow } from "./SitesTable";
 import { AxiosResponse } from "axios";
 import { AxiosResponse } from "axios";
 
 
-const method = [
-    { label: "Newt", value: "newt" },
-    { label: "WireGuard", value: "wireguard" }
-] as const;
-
 const createSiteFormSchema = z.object({
 const createSiteFormSchema = z.object({
     name: z
     name: z
         .string()
         .string()
@@ -74,36 +57,36 @@ const defaultValues: Partial<CreateSiteFormValues> = {
 };
 };
 
 
 type CreateSiteFormProps = {
 type CreateSiteFormProps = {
-    open: boolean;
-    setOpen: (open: boolean) => void;
     onCreate?: (site: SiteRow) => void;
     onCreate?: (site: SiteRow) => void;
+    setLoading?: (loading: boolean) => void;
+    setChecked?: (checked: boolean) => void;
+    orgId: string;
 };
 };
 
 
 export default function CreateSiteForm({
 export default function CreateSiteForm({
-    open,
-    setOpen,
-    onCreate
+    onCreate,
+    setLoading,
+    setChecked,
+    orgId
 }: CreateSiteFormProps) {
 }: CreateSiteFormProps) {
     const { toast } = useToast();
     const { toast } = useToast();
 
 
     const api = createApiClient(useEnvContext());
     const api = createApiClient(useEnvContext());
 
 
-    const [loading, setLoading] = useState(false);
+    const [isLoading, setIsLoading] = useState(false);
+    const [isChecked, setIsChecked] = useState(false);
 
 
-    const params = useParams();
-    const orgId = params.orgId;
     const router = useRouter();
     const router = useRouter();
 
 
     const [keypair, setKeypair] = useState<{
     const [keypair, setKeypair] = useState<{
         publicKey: string;
         publicKey: string;
         privateKey: string;
         privateKey: string;
     } | null>(null);
     } | null>(null);
-    const [isLoading, setIsLoading] = useState(true);
-    const [isChecked, setIsChecked] = useState(false);
     const [siteDefaults, setSiteDefaults] =
     const [siteDefaults, setSiteDefaults] =
         useState<PickSiteDefaultsResponse | null>(null);
         useState<PickSiteDefaultsResponse | null>(null);
 
 
     const handleCheckboxChange = (checked: boolean) => {
     const handleCheckboxChange = (checked: boolean) => {
+        setChecked?.(checked);
         setIsChecked(checked);
         setIsChecked(checked);
     };
     };
 
 
@@ -115,29 +98,35 @@ export default function CreateSiteForm({
     useEffect(() => {
     useEffect(() => {
         if (!open) return;
         if (!open) return;
 
 
-        if (typeof window !== "undefined") {
-            const generatedKeypair = generateKeypair();
-            setKeypair(generatedKeypair);
-            setIsLoading(false);
+        // reset all values
+        setLoading?.(false);
+        setIsLoading(false);
+        form.reset();
+        setChecked?.(false);
+        setKeypair(null);
+        setSiteDefaults(null);
 
 
-            api.get(`/org/${orgId}/pick-site-defaults`)
-                .catch((e) => {
-                    toast({
-                        variant: "destructive",
-                        title: "Error picking site defaults",
-                        description: formatAxiosError(e)
-                    });
-                })
-                .then((res) => {
-                    if (res && res.status === 200) {
-                        setSiteDefaults(res.data.data);
-                    }
+        const generatedKeypair = generateKeypair();
+        setKeypair(generatedKeypair);
+
+        api.get(`/org/${orgId}/pick-site-defaults`)
+            .catch((e) => {
+                toast({
+                    variant: "destructive",
+                    title: "Error picking site defaults",
+                    description: formatAxiosError(e)
                 });
                 });
-        }
+            })
+            .then((res) => {
+                if (res && res.status === 200) {
+                    setSiteDefaults(res.data.data);
+                }
+            });
     }, [open]);
     }, [open]);
 
 
     async function onSubmit(data: CreateSiteFormValues) {
     async function onSubmit(data: CreateSiteFormValues) {
-        setLoading(true);
+        setLoading?.(true);
+        setIsLoading(true);
         if (!siteDefaults || !keypair) {
         if (!siteDefaults || !keypair) {
             return;
             return;
         }
         }
@@ -169,9 +158,6 @@ export default function CreateSiteForm({
             // navigate to the site page
             // navigate to the site page
             // router.push(`/${orgId}/settings/sites/${niceId}`);
             // router.push(`/${orgId}/settings/sites/${niceId}`);
 
 
-            // close the modal
-            setOpen(false);
-
             const data = res.data.data;
             const data = res.data.data;
 
 
             onCreate?.({
             onCreate?.({
@@ -186,7 +172,8 @@ export default function CreateSiteForm({
             });
             });
         }
         }
 
 
-        setLoading(false);
+        setLoading?.(false);
+        setIsLoading(false);
     }
     }
 
 
     const wgConfig =
     const wgConfig =
@@ -212,148 +199,96 @@ PersistentKeepalive = 5`
     const newtConfig = `newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${proto}//${siteDefaults?.endpoint}`;
     const newtConfig = `newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${proto}//${siteDefaults?.endpoint}`;
 
 
     return (
     return (
-        <>
-            <Credenza
-                open={open}
-                onOpenChange={(val) => {
-                    setOpen(val);
-                    setLoading(false);
-
-                    // reset all values
-                    form.reset();
-                    setIsChecked(false);
-                    setKeypair(null);
-                    setSiteDefaults(null);
-                }}
-            >
-                <CredenzaContent>
-                    <CredenzaHeader>
-                        <CredenzaTitle>Create Site</CredenzaTitle>
-                        <CredenzaDescription>
-                            Create a new site to start connecting your resources
-                        </CredenzaDescription>
-                    </CredenzaHeader>
-                    <CredenzaBody>
-                        <div className="space-y-8">
-                            <Form {...form}>
-                                <form
-                                    onSubmit={form.handleSubmit(onSubmit)}
-                                    className="space-y-8"
-                                    id="create-site-form"
-                                >
-                                    <FormField
-                                        control={form.control}
-                                        name="name"
-                                        render={({ field }) => (
-                                            <FormItem>
-                                                <FormLabel>Name</FormLabel>
-                                                <FormControl>
-                                                    <Input
-                                                        autoComplete="off"
-                                                        placeholder="Site name"
-                                                        {...field}
-                                                    />
-                                                </FormControl>
-                                                <FormDescription>
-                                                    This is the name that will
-                                                    be displayed for this site.
-                                                </FormDescription>
-                                                <FormMessage />
-                                            </FormItem>
-                                        )}
-                                    />
-                                    <FormField
-                                        control={form.control}
-                                        name="method"
-                                        render={({ field }) => (
-                                            <FormItem>
-                                                <FormLabel>Method</FormLabel>
-                                                <FormControl>
-                                                    <Select
-                                                        value={field.value}
-                                                        onValueChange={
-                                                            field.onChange
-                                                        }
-                                                    >
-                                                        <SelectTrigger>
-                                                            <SelectValue placeholder="Select method" />
-                                                        </SelectTrigger>
-                                                        <SelectContent>
-                                                            <SelectItem value="wireguard">
-                                                                WireGuard
-                                                            </SelectItem>
-                                                            <SelectItem value="newt">
-                                                                Newt
-                                                            </SelectItem>
-                                                        </SelectContent>
-                                                    </Select>
-                                                </FormControl>
-                                                <FormDescription>
-                                                    This is how you will connect
-                                                    your site to Fossorial.
-                                                </FormDescription>
-                                                <FormMessage />
-                                            </FormItem>
-                                        )}
+        <div className="space-y-8">
+            <Form {...form}>
+                <form
+                    onSubmit={form.handleSubmit(onSubmit)}
+                    className="space-y-8"
+                    id="create-site-form"
+                >
+                    <FormField
+                        control={form.control}
+                        name="name"
+                        render={({ field }) => (
+                            <FormItem>
+                                <FormLabel>Name</FormLabel>
+                                <FormControl>
+                                    <Input
+                                        autoComplete="off"
+                                        placeholder="Site name"
+                                        {...field}
                                     />
                                     />
+                                </FormControl>
+                                <FormDescription>
+                                    This is the name that will be displayed for
+                                    this site.
+                                </FormDescription>
+                                <FormMessage />
+                            </FormItem>
+                        )}
+                    />
+                    <FormField
+                        control={form.control}
+                        name="method"
+                        render={({ field }) => (
+                            <FormItem>
+                                <FormLabel>Method</FormLabel>
+                                <FormControl>
+                                    <Select
+                                        value={field.value}
+                                        onValueChange={field.onChange}
+                                    >
+                                        <SelectTrigger>
+                                            <SelectValue placeholder="Select method" />
+                                        </SelectTrigger>
+                                        <SelectContent>
+                                            <SelectItem value="wireguard">
+                                                WireGuard
+                                            </SelectItem>
+                                            <SelectItem value="newt">
+                                                Newt
+                                            </SelectItem>
+                                        </SelectContent>
+                                    </Select>
+                                </FormControl>
+                                <FormDescription>
+                                    This is how you will expose connections.
+                                </FormDescription>
+                                <FormMessage />
+                            </FormItem>
+                        )}
+                    />
 
 
-                                    <div className="max-w-md">
-                                        {form.watch("method") === "wireguard" &&
-                                        !isLoading ? (
-                                            <CopyTextBox text={wgConfig} />
-                                        ) : form.watch("method") ===
-                                              "wireguard" && isLoading ? (
-                                            <p>
-                                                Loading WireGuard
-                                                configuration...
-                                            </p>
-                                        ) : (
-                                            <CopyTextBox
-                                                text={newtConfig}
-                                                wrapText={false}
-                                            />
-                                        )}
-                                    </div>
+                    <div className="w-full">
+                        {form.watch("method") === "wireguard" && !isLoading ? (
+                            <CopyTextBox text={wgConfig} />
+                        ) : form.watch("method") === "wireguard" &&
+                          isLoading ? (
+                            <p>Loading WireGuard configuration...</p>
+                        ) : (
+                            <CopyTextBox text={newtConfig} wrapText={false} />
+                        )}
+                    </div>
 
 
-                                    <span className="text-sm text-muted-foreground">
-                                        You will only be able to see the
-                                        configuration once.
-                                    </span>
+                    <span className="text-sm text-muted-foreground">
+                        You will only be able to see the configuration once.
+                    </span>
 
 
-                                    <div className="flex items-center space-x-2">
-                                        <Checkbox
-                                            id="terms"
-                                            checked={isChecked}
-                                            onCheckedChange={
-                                                handleCheckboxChange
-                                            }
-                                        />
-                                        <label
-                                            htmlFor="terms"
-                                            className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
-                                        >
-                                            I have copied the config
-                                        </label>
-                                    </div>
-                                </form>
-                            </Form>
-                        </div>
-                    </CredenzaBody>
-                    <CredenzaFooter>
-                        <Button
-                            type="submit"
-                            form="create-site-form"
-                            loading={loading}
-                            disabled={loading || !isChecked}
+                    <div className="flex items-center space-x-2">
+                        <Checkbox
+                            id="terms"
+                            checked={isChecked}
+                            onCheckedChange={handleCheckboxChange}
+                        />
+                        <label
+                            htmlFor="terms"
+                            className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
                         >
                         >
-                            Create Site
-                        </Button>
-                        <CredenzaClose asChild>
-                            <Button variant="outline">Close</Button>
-                        </CredenzaClose>
-                    </CredenzaFooter>
-                </CredenzaContent>
-            </Credenza>
-        </>
+                            I have copied the config
+                        </label>
+                    </div>
+                </form>
+            </Form>
+        </div>
     );
     );
 }
 }

+ 80 - 0
src/app/[orgId]/settings/sites/components/CreateSiteModal.tsx

@@ -0,0 +1,80 @@
+"use client";
+
+import { Button } from "@app/components/ui/button";
+import { useState } from "react";
+import {
+    Credenza,
+    CredenzaBody,
+    CredenzaClose,
+    CredenzaContent,
+    CredenzaDescription,
+    CredenzaFooter,
+    CredenzaHeader,
+    CredenzaTitle
+} from "@app/components/Credenza";
+import { SiteRow } from "./SitesTable";
+import CreateSiteForm from "./CreateSiteForm";
+
+type CreateSiteFormProps = {
+    open: boolean;
+    setOpen: (open: boolean) => void;
+    onCreate?: (site: SiteRow) => void;
+    orgId: string;
+};
+
+export default function CreateSiteFormModal({
+    open,
+    setOpen,
+    onCreate,
+    orgId
+}: CreateSiteFormProps) {
+    const [loading, setLoading] = useState(false);
+    const [isChecked, setIsChecked] = useState(false);
+
+    return (
+        <>
+            <Credenza
+                open={open}
+                onOpenChange={(val) => {
+                    setOpen(val);
+                    setLoading(false);
+                }}
+            >
+                <CredenzaContent>
+                    <CredenzaHeader>
+                        <CredenzaTitle>Create Site</CredenzaTitle>
+                        <CredenzaDescription>
+                            Create a new site to start connecting your resources
+                        </CredenzaDescription>
+                    </CredenzaHeader>
+                    <CredenzaBody>
+                        <div className="max-w-md">
+                            <CreateSiteForm
+                                setLoading={(val) => setLoading(val)}
+                                setChecked={(val) => setIsChecked(val)}
+                                onCreate={onCreate}
+                                orgId={orgId}
+                            />
+                        </div>
+                    </CredenzaBody>
+                    <CredenzaFooter>
+                        <Button
+                            type="submit"
+                            form="create-site-form"
+                            loading={loading}
+                            disabled={loading || !isChecked}
+                            onClick={() => {
+                                setOpen(false);
+                            }}
+                        >
+                            Create Site
+                        </Button>
+                        <CredenzaClose asChild>
+                            <Button variant="outline">Close</Button>
+                        </CredenzaClose>
+                    </CredenzaFooter>
+                </CredenzaContent>
+            </Credenza>
+        </>
+    );
+}

+ 5 - 2
src/app/[orgId]/settings/sites/components/SitesTable.tsx

@@ -26,6 +26,7 @@ import { useToast } from "@app/hooks/useToast";
 import { formatAxiosError } from "@app/lib/utils";
 import { formatAxiosError } from "@app/lib/utils";
 import { createApiClient } from "@app/api";
 import { createApiClient } from "@app/api";
 import { useEnvContext } from "@app/hooks/useEnvContext";
 import { useEnvContext } from "@app/hooks/useEnvContext";
+import CreateSiteFormModal from "./CreateSiteModal";
 
 
 export type SiteRow = {
 export type SiteRow = {
     id: number;
     id: number;
@@ -68,6 +69,8 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
             .then(() => {
             .then(() => {
                 router.refresh();
                 router.refresh();
                 setIsDeleteModalOpen(false);
                 setIsDeleteModalOpen(false);
+
+                const newRows = rows.filter((row) => row.id !== siteId);
             });
             });
     };
     };
 
 
@@ -188,7 +191,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
             },
             },
             cell: ({ row }) => {
             cell: ({ row }) => {
                 const originalRow = row.original;
                 const originalRow = row.original;
-                console.log(originalRow.online);
 
 
                 if (originalRow.online) {
                 if (originalRow.online) {
                     return (
                     return (
@@ -257,12 +259,13 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
 
 
     return (
     return (
         <>
         <>
-            <CreateSiteForm
+            <CreateSiteFormModal
                 open={isCreateModalOpen}
                 open={isCreateModalOpen}
                 setOpen={setIsCreateModalOpen}
                 setOpen={setIsCreateModalOpen}
                 onCreate={(val) => {
                 onCreate={(val) => {
                     setRows([val, ...rows]);
                     setRows([val, ...rows]);
                 }}
                 }}
+                orgId={orgId}
             />
             />
 
 
             {selectedSite && (
             {selectedSite && (

+ 31 - 9
src/app/setup/page.tsx

@@ -32,6 +32,7 @@ import {
     FormMessage
     FormMessage
 } from "@app/components/ui/form";
 } from "@app/components/ui/form";
 import { Alert, AlertDescription } from "@app/components/ui/alert";
 import { Alert, AlertDescription } from "@app/components/ui/alert";
+import CreateSiteForm from "../[orgId]/settings/sites/components/CreateSiteForm";
 
 
 type Step = "org" | "site" | "resources";
 type Step = "org" | "site" | "resources";
 
 
@@ -45,6 +46,7 @@ export default function StepperForm() {
     const [orgIdTaken, setOrgIdTaken] = useState(false);
     const [orgIdTaken, setOrgIdTaken] = useState(false);
 
 
     const [loading, setLoading] = useState(false);
     const [loading, setLoading] = useState(false);
+    const [isChecked, setIsChecked] = useState(false);
     const [error, setError] = useState<string | null>(null);
     const [error, setError] = useState<string | null>(null);
 
 
     const orgForm = useForm<z.infer<typeof orgSchema>>({
     const orgForm = useForm<z.infer<typeof orgSchema>>({
@@ -292,18 +294,38 @@ export default function StepperForm() {
                         )}
                         )}
 
 
                         {currentStep === "site" && (
                         {currentStep === "site" && (
-                            <div className="flex justify-end">
-                                <Button
-                                    type="submit"
-                                    variant="outline"
-                                    onClick={() => {
+                            <div>
+                                <CreateSiteForm
+                                    setLoading={(val) => setLoading(val)}
+                                    setChecked={(val) => setIsChecked(val)}
+                                    orgId={orgForm.getValues().orgId}
+                                    onCreate={() => {
                                         router.push(
                                         router.push(
-                                            `/${orgForm.getValues().orgId}/settings/sites`
+                                            `/${orgForm.getValues().orgId}/settings/resources`
                                         );
                                         );
                                     }}
                                     }}
-                                >
-                                    Skip for now
-                                </Button>
+                                />
+                                <div className="flex justify-between mt-6">
+                                    <Button
+                                        type="submit"
+                                        variant="outline"
+                                        onClick={() => {
+                                            router.push(
+                                                `/${orgForm.getValues().orgId}/settings/sites`
+                                            );
+                                        }}
+                                    >
+                                        Skip for now
+                                    </Button>
+                                    <Button
+                                        type="submit"
+                                        form="create-site-form"
+                                        loading={loading}
+                                        disabled={loading || !isChecked}
+                                    >
+                                        Create Site
+                                    </Button>
+                                </div>
                             </div>
                             </div>
                         )}
                         )}
                     </section>
                     </section>