Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

๐Ÿš€ allow every content type (#996)

* ๐Ÿš€ allow every content type

* ๐Ÿš€ content type

* ๐Ÿš€ content type

* ci: apply automated fixes

* ๐Ÿš€ file upload

* ci: apply automated fixes

* fix: style

* ci: apply automated fixes

* ๐Ÿคฏ

* ci: apply automated fixes

* ๐Ÿ˜ญ

* fix: style

* chore: small stuff

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Kaske <maximilian@kaske.org>

authored by

Thibault Le Ouay
autofix-ci[bot]
Maximilian Kaske
and committed by
GitHub
ce393586 7cef4a1e

+196 -52
+14 -2
apps/checker/ping.go
··· 77 77 } 78 78 } 79 79 if inputData.Method != http.MethodGet { 80 - req.Header.Set("Content-Type", "application/json") 80 + head := req.Header 81 + _, ok := head["Content-Type"] 82 + if !ok { 83 + // by default we set the content type to application/json if it's a POST request 84 + req.Header.Set("Content-Type", "application/json") 85 + } 81 86 } 82 87 83 88 timing := Timing{} ··· 188 193 for key, value := range inputData.Headers { 189 194 req.Header.Set(key, value) 190 195 } 196 + 191 197 if inputData.Method != http.MethodGet { 192 - req.Header.Set("Content-Type", "application/json") 198 + head := req.Header 199 + _, ok := head["Content-Type"] 200 + if !ok { 201 + // by default we set the content type to application/json if it's a POST request 202 + req.Header.Set("Content-Type", "application/json") 203 + } 193 204 } 205 + 194 206 timing := Timing{} 195 207 196 208 trace := &httptrace.ClientTrace{
+7 -1
apps/web/src/components/forms/monitor/form.tsx
··· 178 178 textBodyAssertions, 179 179 } = form.getValues(); 180 180 181 - if (body && body !== "") { 181 + if ( 182 + body && 183 + body !== "" && 184 + headers?.some( 185 + (h) => h.key === "Content-Type" && h.value === "application/json", 186 + ) 187 + ) { 182 188 const validJSON = validateJSON(body); 183 189 if (!validJSON) { 184 190 return { error: "Not a valid JSON object.", data: undefined };
+149 -41
apps/web/src/components/forms/monitor/section-requests.tsx
··· 1 1 "use client"; 2 2 3 3 import { Wand2, X } from "lucide-react"; 4 - import * as React from "react"; 4 + import type * as React from "react"; 5 5 import { useFieldArray } from "react-hook-form"; 6 6 import type { UseFormReturn } from "react-hook-form"; 7 7 ··· 9 9 monitorMethods, 10 10 monitorMethodsSchema, 11 11 } from "@openstatus/db/src/schema"; 12 - import type { InsertMonitor, WorkspacePlan } from "@openstatus/db/src/schema"; 12 + import type { InsertMonitor } from "@openstatus/db/src/schema"; 13 13 import { 14 14 Button, 15 15 FormControl, ··· 31 31 TooltipTrigger, 32 32 } from "@openstatus/ui"; 33 33 34 + import { toast } from "@/lib/toast"; 35 + import { useRef, useState } from "react"; 34 36 import { SectionHeader } from "../shared/section-header"; 35 37 38 + const contentTypes = [ 39 + { value: "application/octet-stream", label: "Binary File" }, 40 + { value: "application/json", label: "JSON" }, 41 + { value: "application/xml", label: "XML" }, 42 + { value: "application/yaml", label: "YAML" }, 43 + { value: "application/edn", label: "EDN" }, 44 + { value: "application/other", label: "Other" }, 45 + { value: "none", label: "None" }, 46 + ]; 47 + 36 48 interface Props { 37 49 form: UseFormReturn<InsertMonitor>; 38 50 } ··· 40 52 // TODO: add Dialog with response informations when pingEndpoint! 41 53 42 54 export function SectionRequests({ form }: Props) { 43 - const { fields, append, remove } = useFieldArray({ 55 + const { fields, append, prepend, remove, update } = useFieldArray({ 44 56 name: "headers", 45 57 control: form.control, 46 58 }); 59 + const inputRef = useRef<HTMLInputElement>(null); 47 60 const watchMethod = form.watch("method"); 61 + const [file, setFile] = useState<string | undefined>(undefined); 62 + const [content, setContent] = useState<string | undefined>( 63 + fields.find((field) => field.key === "Content-Type")?.value, 64 + ); 48 65 49 66 const validateJSON = (value?: string) => { 50 67 if (!value) return; ··· 60 77 } 61 78 }; 62 79 80 + const uploadFile = async (event: React.ChangeEvent<HTMLInputElement>) => { 81 + if (event.target.files?.[0]) { 82 + const file = event.target.files[0]; 83 + 84 + // File too big, return error 85 + const fileSize = file.size / 1024 / 1024; // in MiB 86 + if (fileSize > 10) { 87 + // Display error message 88 + toast.error("File size is too big. Max 10MB allowed."); 89 + return; 90 + } 91 + 92 + const reader = new FileReader(); 93 + reader.onload = (event) => { 94 + if (event.target?.result && typeof event.target.result === "string") { 95 + form.setValue("body", event.target?.result); 96 + setFile(file.name); 97 + } 98 + }; 99 + 100 + reader.readAsDataURL(file); 101 + } 102 + }; 103 + 63 104 const onPrettifyJSON = () => { 64 105 const body = form.getValues("body"); 65 106 const obj = validateJSON(body); ··· 86 127 onValueChange={(value) => { 87 128 field.onChange(monitorMethodsSchema.parse(value)); 88 129 form.resetField("body", { defaultValue: "" }); 130 + setContent(undefined); 89 131 }} 90 132 defaultValue={field.value} 91 133 > ··· 113 155 <FormItem className="sm:col-span-6"> 114 156 <FormLabel>URL</FormLabel> 115 157 <FormControl> 116 - {/* <InputWithAddons 117 - leading="https://" 118 - placeholder="documenso.com/api/health" 119 - {...field} 120 - /> */} 121 158 <Input 122 159 className="bg-muted" 123 160 placeholder="https://documenso.com/api/health" 124 161 {...field} 125 162 /> 126 163 </FormControl> 127 - {/* <FormMessage /> */} 128 164 </FormItem> 129 165 )} 130 166 /> ··· 160 196 size="icon" 161 197 variant="ghost" 162 198 type="button" 163 - onClick={() => remove(Number(field.id))} 199 + onClick={() => { 200 + remove(index); 201 + }} 164 202 > 165 203 <X className="h-4 w-4" /> 166 204 </Button> ··· 183 221 control={form.control} 184 222 name="body" 185 223 render={({ field }) => ( 186 - <FormItem> 224 + <FormItem className="space-y-1.5"> 187 225 <div className="flex items-end justify-between"> 188 - <FormLabel>Body</FormLabel> 189 - <TooltipProvider> 190 - <Tooltip> 191 - <TooltipTrigger asChild> 226 + <FormLabel className="flex items-center space-x-2"> 227 + Body 228 + <Select 229 + defaultValue={content} 230 + onValueChange={(value) => { 231 + setContent(value); 232 + 233 + if (content === "application/octet-stream") { 234 + form.setValue("body", ""); 235 + setFile(undefined); 236 + } 237 + 238 + const contentIndex = fields.findIndex( 239 + (field) => field.key === "Content-Type", 240 + ); 241 + 242 + if (contentIndex >= 0) { 243 + if (value === "none") { 244 + remove(contentIndex); 245 + } else { 246 + update(contentIndex, { 247 + key: "Content-Type", 248 + value, 249 + }); 250 + } 251 + } else { 252 + prepend({ key: "Content-Type", value }); 253 + } 254 + }} 255 + > 256 + <SelectTrigger 257 + variant={"ghost"} 258 + className="ml-1 h-7 text-muted-foreground text-xs" 259 + > 260 + <SelectValue placeholder="Content-Type" /> 261 + </SelectTrigger> 262 + <SelectContent> 263 + {contentTypes.map((type) => ( 264 + <SelectItem key={type.value} value={type.value}> 265 + {type.label} 266 + </SelectItem> 267 + ))} 268 + </SelectContent> 269 + </Select> 270 + </FormLabel> 271 + {watchMethod === "POST" && 272 + fields.some( 273 + (field) => 274 + field.key === "Content-Type" && 275 + field.value === "application/json", 276 + ) && ( 277 + <TooltipProvider> 278 + <Tooltip> 279 + <TooltipTrigger asChild> 280 + <Button 281 + type="button" 282 + variant="ghost" 283 + size="icon" 284 + className="h-7 w-7" 285 + onClick={onPrettifyJSON} 286 + > 287 + <Wand2 className="h-3 w-3" /> 288 + </Button> 289 + </TooltipTrigger> 290 + <TooltipContent> 291 + <p>Prettify JSON</p> 292 + </TooltipContent> 293 + </Tooltip> 294 + </TooltipProvider> 295 + )} 296 + </div> 297 + <div className="space-y-2"> 298 + <FormControl> 299 + {content === "application/octet-stream" ? ( 300 + <> 192 301 <Button 193 302 type="button" 194 - variant="ghost" 195 - size="icon" 196 - onClick={onPrettifyJSON} 303 + variant="outline" 304 + onClick={() => inputRef.current?.click()} 305 + className="max-w-56" 197 306 > 198 - <Wand2 className="h-4 w-4" /> 307 + <span className="truncate"> 308 + {file || form.getValues("body") || "Upload file"} 309 + </span> 199 310 </Button> 200 - </TooltipTrigger> 201 - <TooltipContent> 202 - <p>Prettify JSON</p> 203 - </TooltipContent> 204 - </Tooltip> 205 - </TooltipProvider> 311 + <input 312 + type="file" 313 + onChange={uploadFile} 314 + ref={inputRef} 315 + hidden 316 + /> 317 + </> 318 + ) : ( 319 + <> 320 + <Textarea 321 + rows={8} 322 + placeholder='{ "hello": "world" }' 323 + {...field} 324 + /> 325 + <FormDescription>Write your payload.</FormDescription> 326 + </> 327 + )} 328 + </FormControl> 329 + <FormMessage /> 206 330 </div> 207 - <FormControl> 208 - {/* FIXME: cannot enter 'Enter' */} 209 - <Textarea 210 - rows={8} 211 - placeholder='{ "hello": "world" }' 212 - {...field} 213 - /> 214 - </FormControl> 215 - <FormDescription> 216 - Write your json payload. We automatically append{" "} 217 - <code> 218 - &quot;Content-Type&quot;: &quot;application/json&quot; 219 - </code>{" "} 220 - to the request header. 221 - </FormDescription> 222 - <FormMessage /> 223 331 </FormItem> 224 332 )} 225 333 />
+1
packages/db/src/schema/monitors/validation.ts
··· 57 57 .optional(); 58 58 59 59 export const insertMonitorSchema = createInsertSchema(monitor, { 60 + name: z.string().min(1, "Name must be at least 1 character long"), 60 61 periodicity: monitorPeriodicitySchema.default("10m"), 61 62 url: z.string().url(), // find a better way to not always start with "https://" including the `InputWithAddons` 62 63 status: monitorStatusSchema.default("active"),
+25 -8
packages/ui/src/components/select.tsx
··· 5 5 import * as React from "react"; 6 6 7 7 import { cn } from "../lib/utils"; 8 + import { cva, VariantProps } from "class-variance-authority"; 8 9 9 10 const Select = SelectPrimitive.Root; 10 11 ··· 12 13 13 14 const SelectValue = SelectPrimitive.Value; 14 15 16 + const selectVariants = cva( 17 + "flex w-full h-10 items-center justify-between px-3 rounded-md border border-input ring-offset-background py-2 text-sm placeholder:text-muted-foreground focus:ring-ring focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", 18 + { 19 + variants: { 20 + variant: { 21 + default: "bg-transparent", 22 + 23 + ghost: "border-none space-x-2", 24 + }, 25 + }, 26 + defaultVariants: { 27 + variant: "default", 28 + }, 29 + } 30 + ); 31 + export interface SelectTriggerProps 32 + extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>, 33 + VariantProps<typeof selectVariants> {} 34 + 15 35 const SelectTrigger = React.forwardRef< 16 36 React.ElementRef<typeof SelectPrimitive.Trigger>, 17 - React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> 37 + SelectTriggerProps 18 38 >(({ className, children, ...props }, ref) => ( 19 39 <SelectPrimitive.Trigger 20 40 ref={ref} 21 - className={cn( 22 - "border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border bg-transparent px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", 23 - className, 24 - )} 41 + className={cn(selectVariants({ variant: props.variant }), className)} 25 42 {...props} 26 43 > 27 44 {children} ··· 43 60 "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md", 44 61 position === "popper" && 45 62 "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", 46 - className, 63 + className 47 64 )} 48 65 position={position} 49 66 {...props} ··· 52 69 className={cn( 53 70 "p-1", 54 71 position === "popper" && 55 - "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]", 72 + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" 56 73 )} 57 74 > 58 75 {children} ··· 82 99 ref={ref} 83 100 className={cn( 84 101 "focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 85 - className, 102 + className 86 103 )} 87 104 {...props} 88 105 >