Openstatus
www.openstatus.dev
1"use client";
2
3import { FormCardGroup } from "@/components/forms/form-card";
4import { useTRPC } from "@/lib/trpc/client";
5import { deserialize } from "@openstatus/assertions";
6import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7import { useParams, useRouter } from "next/navigation";
8import { FormDangerZone } from "./form-danger-zone";
9import {
10 FOLLOW_REDIRECTS_DEFAULT,
11 FormFollowRedirect,
12} from "./form-follow-redirect";
13import { FormGeneral } from "./form-general";
14import { FormNotifiers } from "./form-notifiers";
15import { FormOtel } from "./form-otel";
16import { FormResponseTime } from "./form-response-time";
17import { FormRetry, RETRY_DEFAULT } from "./form-retry";
18import { FormSchedulingRegions } from "./form-scheduling-regions";
19import { FormStatusPages } from "./form-status-pages";
20import { FormTags } from "./form-tags";
21import { FormVisibility } from "./form-visibility";
22
23export function FormMonitorUpdate() {
24 const { id } = useParams<{ id: string }>();
25 const trpc = useTRPC();
26 const router = useRouter();
27 const queryClient = useQueryClient();
28 const { data: monitor, refetch } = useQuery(
29 trpc.monitor.get.queryOptions({ id: Number.parseInt(id) }),
30 );
31 const { data: statusPages } = useQuery(trpc.page.list.queryOptions());
32 const { data: privateLocations } = useQuery(
33 trpc.privateLocation.list.queryOptions(),
34 );
35 const { data: notifications } = useQuery(
36 trpc.notification.list.queryOptions(),
37 );
38 const { data: workspace } = useQuery(trpc.workspace.get.queryOptions());
39 const updateRetryMutation = useMutation(
40 trpc.monitor.updateRetry.mutationOptions({
41 onSuccess: () => refetch(),
42 }),
43 );
44 const updateOtelMutation = useMutation(
45 trpc.monitor.updateOtel.mutationOptions({
46 onSuccess: () => refetch(),
47 }),
48 );
49 const updatePublicMutation = useMutation(
50 trpc.monitor.updatePublic.mutationOptions({
51 onSuccess: () => refetch(),
52 }),
53 );
54 const updateSchedulingRegionsMutation = useMutation(
55 trpc.monitor.updateSchedulingRegions.mutationOptions({
56 onSuccess: () => refetch(),
57 }),
58 );
59 const updateResponseTimeMutation = useMutation(
60 trpc.monitor.updateResponseTime.mutationOptions({
61 onSuccess: () => refetch(),
62 }),
63 );
64 const updateTagsMutation = useMutation(
65 trpc.monitor.updateTags.mutationOptions({
66 onSuccess: () => refetch(),
67 }),
68 );
69 const updateFollowRedirectsMutation = useMutation(
70 trpc.monitor.updateFollowRedirects.mutationOptions({
71 onSuccess: () => refetch(),
72 }),
73 );
74
75 const updateGeneralMutation = useMutation(
76 trpc.monitor.updateGeneral.mutationOptions({
77 onSuccess: () => {
78 // NOTE: invalidate the list query to update the monitor in the list (especially the name)
79 queryClient.invalidateQueries({
80 queryKey: trpc.monitor.list.queryKey(),
81 });
82 refetch();
83 },
84 onError: (err) => {
85 // TODO: open dialog
86 console.error(err);
87 },
88 }),
89 );
90
91 const updateStatusPagesMutation = useMutation(
92 trpc.monitor.updateStatusPages.mutationOptions({
93 onSuccess: () => refetch(),
94 }),
95 );
96
97 const updateNotifiersMutation = useMutation(
98 trpc.monitor.updateNotifiers.mutationOptions({
99 onSuccess: () => refetch(),
100 }),
101 );
102
103 const deleteMonitorMutation = useMutation(
104 trpc.monitor.delete.mutationOptions({
105 onSuccess: () => {
106 queryClient.invalidateQueries({
107 queryKey: trpc.monitor.list.queryKey(),
108 });
109 router.push("/monitors");
110 },
111 }),
112 );
113
114 if (
115 !monitor ||
116 !statusPages ||
117 !notifications ||
118 !workspace ||
119 !privateLocations
120 )
121 return null;
122
123 return (
124 <FormCardGroup>
125 <FormGeneral
126 defaultValues={{
127 type: monitor.jobType as "http" | "tcp",
128 url: monitor.url,
129 name: monitor.name,
130 method: monitor.method as "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
131 headers: monitor.headers ?? [],
132 body: monitor.body,
133 active: monitor.active ?? true,
134 // TODO: move to server after migration
135 assertions: monitor?.assertions
136 ? deserialize(monitor?.assertions).map((a) => a.schema)
137 : [],
138 skipCheck: false,
139 saveCheck: false,
140 }}
141 onSubmit={async (values) => {
142 await updateGeneralMutation.mutateAsync({
143 id: Number.parseInt(id),
144 name: values.name,
145 jobType: values.type,
146 url: values.url,
147 method: values.method,
148 headers: values.headers,
149 body: values.body,
150 assertions: values.assertions,
151 skipCheck: values.skipCheck,
152 saveCheck: values.saveCheck,
153 active: values.active,
154 });
155 }}
156 />
157 <FormResponseTime
158 defaultValues={{
159 timeout: monitor.timeout,
160 degradedAfter: monitor.degradedAfter ?? undefined,
161 }}
162 onSubmit={async (values) => {
163 await updateResponseTimeMutation.mutateAsync({
164 id: Number.parseInt(id),
165 timeout: values.timeout,
166 degradedAfter: values.degradedAfter ?? undefined,
167 });
168 }}
169 />
170 <FormTags
171 defaultValues={{
172 tags: monitor.tags,
173 }}
174 onSubmit={async (values) => {
175 await updateTagsMutation.mutateAsync({
176 id: Number.parseInt(id),
177 tags: values.tags.map((tag) => tag.id),
178 });
179 }}
180 />
181 <FormSchedulingRegions
182 privateLocations={privateLocations}
183 defaultValues={{
184 regions: monitor.regions,
185 periodicity: monitor.periodicity,
186 privateLocations: monitor.privateLocations.map(({ id }) => id),
187 }}
188 onSubmit={async (values) => {
189 await updateSchedulingRegionsMutation.mutateAsync({
190 id: Number.parseInt(id),
191 regions: values.regions,
192 periodicity: values.periodicity,
193 privateLocations: values.privateLocations,
194 });
195 }}
196 />
197 <FormStatusPages
198 statusPages={statusPages}
199 defaultValues={{
200 statusPages: monitor.pages.map(({ id }) => id),
201 description: monitor.description,
202 externalName: monitor.externalName ?? "",
203 }}
204 onSubmit={async (values) => {
205 await updateStatusPagesMutation.mutateAsync({
206 id: Number.parseInt(id),
207 statusPages: values.statusPages,
208 description: values.description,
209 externalName: values.externalName,
210 });
211 }}
212 />
213 <FormNotifiers
214 notifiers={notifications}
215 defaultValues={{
216 notifiers: monitor.notifications.map(({ id }) => id),
217 }}
218 onSubmit={async (values) => {
219 await updateNotifiersMutation.mutateAsync({
220 id: Number.parseInt(id),
221 notifiers: values.notifiers,
222 });
223 }}
224 />
225 <FormRetry
226 defaultValues={{
227 retry: monitor.retry ?? RETRY_DEFAULT,
228 }}
229 onSubmit={async (values) =>
230 await updateRetryMutation.mutateAsync({
231 id: Number.parseInt(id),
232 retry: values.retry ?? RETRY_DEFAULT,
233 })
234 }
235 />
236 <FormFollowRedirect
237 defaultValues={{
238 followRedirects: monitor.followRedirects ?? FOLLOW_REDIRECTS_DEFAULT,
239 }}
240 onSubmit={async (values) =>
241 await updateFollowRedirectsMutation.mutateAsync({
242 id: Number.parseInt(id),
243 followRedirects: values.followRedirects ?? FOLLOW_REDIRECTS_DEFAULT,
244 })
245 }
246 />
247 <FormOtel
248 locked={workspace.limits.otel === false}
249 defaultValues={{
250 endpoint: monitor.otelEndpoint ?? "",
251 headers: monitor.otelHeaders ?? [],
252 }}
253 onSubmit={async (values) => {
254 await updateOtelMutation.mutateAsync({
255 id: Number.parseInt(id),
256 otelEndpoint: values.endpoint,
257 otelHeaders: values.headers,
258 });
259 }}
260 />
261 <FormVisibility
262 defaultValues={{
263 visibility: monitor.public ?? false,
264 }}
265 onSubmit={async (values) => {
266 await updatePublicMutation.mutateAsync({
267 id: Number.parseInt(id),
268 public: values.visibility,
269 });
270 }}
271 />
272 <FormDangerZone
273 title={monitor.name}
274 onSubmit={async () => {
275 await deleteMonitorMutation.mutateAsync({ id: Number.parseInt(id) });
276 }}
277 />
278 </FormCardGroup>
279 );
280}