diff --git a/app/page.tsx b/app/page.tsx
index fca34ca..08076be 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -36,8 +36,8 @@ export default function Home() {
-
+
)
-}
+}
\ No newline at end of file
diff --git a/components/EditRoute.tsx b/components/EditRoute.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/components/ListItem.tsx b/components/ListItem.tsx
index 07d4ba6..14dffab 100644
--- a/components/ListItem.tsx
+++ b/components/ListItem.tsx
@@ -1,6 +1,6 @@
import { cn } from "@/lib/utils"
-import { NavigationMenuLink } from "@radix-ui/react-navigation-menu"
import React from "react"
+import { NavigationMenu, NavigationMenuLink } from "./ui/navigation-menu"
const ListItem = React.forwardRef<
React.ElementRef<"a">,
diff --git a/components/RouteCards.tsx b/components/RouteCards.tsx
index 1c3ecc7..d9f083a 100644
--- a/components/RouteCards.tsx
+++ b/components/RouteCards.tsx
@@ -4,13 +4,20 @@ import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "./ui/card";
import { getHosts, getRouteUpstreams } from '@/lib/utils';
import { toast } from './ui/use-toast';
-import React from 'react';
+import React, { useState } from 'react';
import useSWRMutation from 'swr/mutation';
import { deleteRoute } from '@/lib/clientActions';
+import { Dialog, DialogContent, DialogHeader } from './ui/dialog';
+import { DialogDescription, DialogTitle } from '@radix-ui/react-dialog';
+import { Form, FormControl, FormField, FormItem, FormLabel } from './ui/form';
+import { useForm } from 'react-hook-form';
+import { Input } from './ui/input';
export default function RouteCards({ routes }: { routes: Route[] }) {
- const { trigger } = useSWRMutation("/api/caddy/routes", deleteRoute, {
+ const [route, setRoute] = useState();
+
+ const { trigger: handleDelete } = useSWRMutation("/api/caddy/routes", deleteRoute, {
onError: () => {
return toast({
title: "Error",
@@ -27,30 +34,102 @@ export default function RouteCards({ routes }: { routes: Route[] }) {
}
});
+
+ let editRoute = (route: Route) => {
+ setRoute(route);
+ }
+
return (
- routes.map((route, index) => (
-
-
-
- {getHosts(route).values().next().value}
- {getHosts(route).map((route => {route} CardDescription>))}
-
-
-
-
- Tunneling to
-
- {getRouteUpstreams(route)?.map((upstream => upstream.dial))}
-
-
-
-
-
-
-
- ))
+ <>
+
+ {
+ routes.map((route, index) => (
+
+
+
+ {getHosts(route).values().next().value}
+ {getHosts(route).map((route => {route} CardDescription>))}
+
+
+
+
+ Tunneling to
+
+ {getRouteUpstreams(route)?.map((upstream => upstream.dial))}
+
+
+
+
+
+
+
+ ))}
+ >
)
}
+export function EditRouteDialog({ route }: { route?: Route }) {
+ const form = useForm();
+ let onSubmit = ((data: any) => {
+ console.log(JSON.stringify(data))
+ })
+ let targetRoute: Route = route as Route;
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx
new file mode 100644
index 0000000..ab1b621
--- /dev/null
+++ b/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { Cross2Icon } from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/components/ui/form.tsx b/components/ui/form.tsx
new file mode 100644
index 0000000..f6afdaf
--- /dev/null
+++ b/components/ui/form.tsx
@@ -0,0 +1,176 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext()
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField()
+ const body = error ? String(error?.message) : children
+
+ if (!body) {
+ return null
+ }
+
+ return (
+
+ {body}
+
+ )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/components/ui/input.tsx b/components/ui/input.tsx
new file mode 100644
index 0000000..a92b8e0
--- /dev/null
+++ b/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/components/ui/label.tsx b/components/ui/label.tsx
new file mode 100644
index 0000000..5341821
--- /dev/null
+++ b/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/lib/clientActions.ts b/lib/clientActions.ts
index e38681d..7cd39ef 100644
--- a/lib/clientActions.ts
+++ b/lib/clientActions.ts
@@ -1,9 +1,7 @@
import useSWR from "swr";
export async function useConfig() {
- const { data, error, isLoading } = useSWR('/api/caddy/servers', fetcher, {
- refreshInterval: 1000,
- });
+ const { data, error, isLoading } = useSWR('/api/caddy/servers', fetcher);
return {
data,
error,
@@ -12,10 +10,7 @@ export async function useConfig() {
}
export async function useServer() {
- const { data, error, isLoading } = useSWR('/api/caddy/servers', fetcher, {
- refreshInterval: 1000,
- });
-
+ const { data, error, isLoading } = useSWR('/api/caddy/servers', fetcher);
return {
data,
error,
@@ -24,10 +19,7 @@ export async function useConfig() {
}
export async function useUpstreamRoutes(route: Route) {
- const { data, error, isLoading } = useSWR('http://192.168.1.69:2019/reverse_proxy/upstreams', fetcher, {
- refreshInterval: 1000,
- });
-
+ const { data, error, isLoading } = useSWR('http://192.168.1.69:2019/reverse_proxy/upstreams', fetcher);
return {
data,
error,
@@ -37,10 +29,7 @@ export async function useConfig() {
}
export function useRoutes() {
- const { data, error, isLoading } = useSWR('/api/caddy/routes', fetcher, {
- refreshInterval: 1000,
- });
-
+ const { data, error, isLoading } = useSWR('/api/caddy/routes', fetcher);
return {
data,
error,
diff --git a/package.json b/package.json
index 114db54..0a1450c 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,12 @@
"lint": "next lint"
},
"dependencies": {
+ "@hookform/resolvers": "^3.3.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
@@ -21,9 +24,11 @@
"next-themes": "^0.2.1",
"react": "^18",
"react-dom": "^18",
+ "react-hook-form": "^7.48.2",
"swr": "^2.2.4",
"tailwind-merge": "^2.0.0",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20",