Openstatus www.openstatus.dev
at main 208 lines 6.6 kB view raw
1"use client"; 2 3import { Link } from "@/components/common/link"; 4import { 5 FormCard, 6 FormCardContent, 7 FormCardDescription, 8 FormCardFooter, 9 FormCardFooterInfo, 10 FormCardHeader, 11 FormCardTitle, 12 FormCardUpgrade, 13} from "@/components/forms/form-card"; 14import { Button } from "@/components/ui/button"; 15import { 16 Form, 17 FormControl, 18 FormField, 19 FormItem, 20 FormLabel, 21 FormMessage, 22} from "@/components/ui/form"; 23import { Input } from "@/components/ui/input"; 24import { zodResolver } from "@hookform/resolvers/zod"; 25import { Lock, Plus, X } from "lucide-react"; 26import NextLink from "next/link"; 27import { useTransition } from "react"; 28import { useForm } from "react-hook-form"; 29import { toast } from "sonner"; 30import { z } from "zod"; 31 32// TODO: add headers 33 34const schema = z.object({ 35 endpoint: z.url("Please enter a valid URL"), 36 headers: z 37 .array(z.object({ key: z.string(), value: z.string() })) 38 .prefault([]), 39}); 40 41type FormValues = z.input<typeof schema>; 42 43export function FormOtel({ 44 locked, 45 defaultValues, 46 onSubmit, 47 ...props 48}: Omit<React.ComponentProps<"form">, "onSubmit"> & { 49 locked?: boolean; 50 defaultValues?: FormValues; 51 onSubmit: (values: FormValues) => Promise<void>; 52}) { 53 const form = useForm<FormValues>({ 54 resolver: zodResolver(schema), 55 defaultValues: defaultValues ?? { endpoint: "", headers: [] }, 56 }); 57 const [isPending, startTransition] = useTransition(); 58 59 function submitAction(values: FormValues) { 60 if (isPending) return; 61 62 startTransition(async () => { 63 try { 64 const promise = onSubmit(values); 65 toast.promise(promise, { 66 loading: "Saving...", 67 success: "Saved", 68 error: "Failed to save", 69 }); 70 await promise; 71 } catch (error) { 72 console.error(error); 73 } 74 }); 75 } 76 77 return ( 78 <Form {...form}> 79 <form onSubmit={form.handleSubmit(submitAction)} {...props}> 80 <FormCard> 81 {locked ? <FormCardUpgrade /> : null} 82 <FormCardHeader> 83 <FormCardTitle>OpenTelemetry</FormCardTitle> 84 <FormCardDescription> 85 Configure your OpenTelemetry Exporter. 86 </FormCardDescription> 87 </FormCardHeader> 88 <FormCardContent className="grid grid-cols-4 gap-4"> 89 <FormField 90 control={form.control} 91 name="endpoint" 92 render={({ field }) => ( 93 <FormItem className="col-span-full"> 94 <FormLabel>Endpoint</FormLabel> 95 <FormControl> 96 <Input 97 placeholder="https://otel.openstatus.dev/api/v1/metrics" 98 disabled={locked} 99 {...field} 100 /> 101 </FormControl> 102 <FormMessage /> 103 </FormItem> 104 )} 105 /> 106 <FormField 107 control={form.control} 108 name="headers" 109 disabled={locked} 110 render={({ field }) => ( 111 <FormItem className="col-span-full"> 112 <FormLabel>Request Headers</FormLabel> 113 {field.value?.map((header, index) => ( 114 <div key={index} className="grid gap-2 sm:grid-cols-5"> 115 <Input 116 placeholder="Key" 117 className="col-span-2" 118 value={header.key} 119 disabled={locked} 120 onChange={(e) => { 121 const newHeaders = [...(field.value ?? [])]; 122 newHeaders[index] = { 123 ...newHeaders[index], 124 key: e.target.value, 125 }; 126 field.onChange(newHeaders); 127 }} 128 /> 129 <Input 130 placeholder="Value" 131 className="col-span-2" 132 value={header.value} 133 disabled={locked} 134 onChange={(e) => { 135 const newHeaders = [...(field.value ?? [])]; 136 newHeaders[index] = { 137 ...newHeaders[index], 138 value: e.target.value, 139 }; 140 field.onChange(newHeaders); 141 }} 142 /> 143 <Button 144 size="icon" 145 variant="ghost" 146 onClick={() => { 147 const newHeaders = field.value?.filter( 148 (_, i) => i !== index, 149 ); 150 field.onChange(newHeaders); 151 }} 152 > 153 <X /> 154 </Button> 155 </div> 156 ))} 157 <div> 158 <Button 159 size="sm" 160 variant="outline" 161 type="button" 162 disabled={locked} 163 onClick={() => { 164 field.onChange([ 165 ...(field.value ?? []), 166 { key: "", value: "" }, 167 ]); 168 }} 169 > 170 <Plus /> 171 Add Header 172 </Button> 173 </div> 174 <FormMessage /> 175 </FormItem> 176 )} 177 /> 178 </FormCardContent> 179 <FormCardFooter> 180 <FormCardFooterInfo> 181 Learn more about{" "} 182 <Link 183 href="https://docs.openstatus.dev/reference/http-monitor/#opentelemetry" 184 rel="noreferrer" 185 target="_blank" 186 > 187 OTel 188 </Link> 189 . 190 </FormCardFooterInfo> 191 {locked ? ( 192 <Button asChild> 193 <NextLink href="/settings/billing"> 194 <Lock className="size-4" /> 195 Upgrade 196 </NextLink> 197 </Button> 198 ) : ( 199 <Button type="submit" disabled={isPending}> 200 {isPending ? "Submitting..." : "Submit"} 201 </Button> 202 )} 203 </FormCardFooter> 204 </FormCard> 205 </form> 206 </Form> 207 ); 208}