Openstatus
www.openstatus.dev
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}