Home / Function/ LogDrainDestinationSheetForm() — supabase Function Reference

LogDrainDestinationSheetForm() — supabase Function Reference

Architecture documentation for the LogDrainDestinationSheetForm() function in LogDrainDestinationSheetForm.tsx from the supabase codebase.

Entity Profile

Relationship Graph

Source Code

apps/studio/components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx lines 172–780

export function LogDrainDestinationSheetForm({
  open,
  onOpenChange,
  defaultValues,
  onSubmit,
  isLoading,
  mode,
}: {
  open: boolean
  onOpenChange: (v: boolean) => void
  defaultValues?: DefaultValues
  isLoading?: boolean
  onSubmit: (values: z.infer<typeof formSchema>) => void
  mode: 'create' | 'update'
}) {
  // NOTE(kamil): This used to be `any` for a long long time, but after moving to Zod,
  // it produces a correct union type of all possible configs. Unfortunately, this type was not designed correctly
  // and it does not include `type` inside the config itself, so it's not trivial to create `discriminatedUnion`
  // out of it, therefore for an ease of use now, we bail to `any` until the better time come.
  const defaultConfig = (defaultValues?.config || {}) as any
  const CREATE_DEFAULT_HEADERS = {
    'Content-Type': 'application/json',
  }
  const DEFAULT_HEADERS = mode === 'create' ? CREATE_DEFAULT_HEADERS : defaultConfig?.headers || {}

  const sentryEnabled = useFlag('SentryLogDrain')
  const s3Enabled = useFlag('S3logdrain')
  const axiomEnabled = useFlag('axiomLogDrain')
  const last9Enabled = useFlag('Last9LogDrain')

  const { ref } = useParams()
  const { data: logDrains } = useLogDrainsQuery({
    ref,
  })

  const defaultType = defaultValues?.type || 'webhook'
  const [newCustomHeader, setNewCustomHeader] = useState({ name: '', value: '' })
  const track = useTrack()

  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    values: {
      name: defaultValues?.name || '',
      description: defaultValues?.description || '',
      type: defaultType,
      http: defaultConfig?.http || 'http2',
      gzip: mode === 'create' ? true : defaultConfig?.gzip || false,
      headers: DEFAULT_HEADERS,
      url: defaultConfig?.url || '',
      api_key: defaultConfig?.api_key || '',
      region: defaultConfig?.region || '',
      username: defaultConfig?.username || '',
      password: defaultConfig?.password || '',
      dsn: defaultConfig?.dsn || '',
      s3_bucket: defaultConfig?.s3_bucket || '',
      storage_region: defaultConfig?.storage_region || '',
      access_key_id: defaultConfig?.access_key_id || '',
      secret_access_key: defaultConfig?.secret_access_key || '',
      batch_timeout: defaultConfig?.batch_timeout ?? 3000,
      dataset_name: defaultConfig?.dataset_name || '',
      api_token: defaultConfig?.api_token || '',
    },
  })

  const headers = form.watch('headers')
  const type = form.watch('type')

  function removeHeader(key: string) {
    const newHeaders = {
      ...headers,
    }
    delete newHeaders[key]
    form.setValue('headers', newHeaders)
  }

  function addHeader() {
    const formHeaders = form.getValues('headers')
    if (!formHeaders) return
    const headerKeys = Object.keys(formHeaders)
    if (headerKeys?.length === 20) {
      toast.error('You can only have 20 custom headers')
      return
    }
    if (headerKeys?.includes(newCustomHeader.name)) {
      toast.error('Header name already exists')
      return
    }
    if (!newCustomHeader.name || !newCustomHeader.value) {
      toast.error('Header name and value are required')
      return
    }
    form.setValue('headers', { ...formHeaders, [newCustomHeader.name]: newCustomHeader.value })
    setNewCustomHeader({ name: '', value: '' })
  }

  const hasHeaders = Object.keys(headers || {})?.length > 0

  useEffect(() => {
    if (mode === 'create' && !open) {
      form.reset()
    }
  }, [mode, open, form])

  function getHeadersSectionDescription() {
    if (type === 'webhook') {
      return 'Set custom headers when draining logs to the Endpoint URL'
    }
    if (type === 'loki') {
      return 'Set custom headers when draining logs to the Loki HTTP(S) endpoint'
    }
    return ''
  }

  return (
    <Sheet
      open={open}
      onOpenChange={(v) => {
        setNewCustomHeader({ name: '', value: '' })
        onOpenChange(v)
      }}
    >
      <SheetContent
        tabIndex={undefined}
        showClose={false}
        size="lg"
        className="overflow-y-auto flex flex-col"
      >
        <SheetHeader>
          <SheetTitle>Add destination</SheetTitle>
        </SheetHeader>
        <SheetSection className="!px-0 !pb-0">
          <Form_Shadcn_ {...form}>
            <form
              id={FORM_ID}
              onSubmit={(e) => {
                e.preventDefault()

                // Temp check to make sure the name is unique
                const logDrainName = form.getValues('name')
                const logDrainExists =
                  !!logDrains?.length && logDrains?.find((drain) => drain.name === logDrainName)
                if (logDrainExists && mode === 'create') {
                  toast.error('Log drain name already exists')
                  return
                }

                form.handleSubmit(onSubmit)(e)
                track('log_drain_save_button_clicked', {
                  destination: form.getValues('type'),
                })
              }}
            >
              <div className="space-y-8 px-content">
                <LogDrainFormItem
                  value="name"
                  placeholder="My Destination"
                  label="Name"
                  formControl={form.control}
                />
                <LogDrainFormItem
                  value="description"
                  placeholder="Optional description"
                  label="Description"
                  formControl={form.control}
                />
                {mode === 'create' && (
                  <FormItemLayout
                    layout="horizontal"
                    label="Type"
                    description={LOG_DRAIN_TYPES.find((t) => t.value === type)?.description || ''}
                  >
                    <Select_Shadcn_
                      defaultValue={defaultType}
                      value={form.getValues('type')}
                      onValueChange={(v: LogDrainType) => form.setValue('type', v)}
                    >
                      <SelectTrigger_Shadcn_>
                        {LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
                      </SelectTrigger_Shadcn_>
                      <SelectContent_Shadcn_>
                        {LOG_DRAIN_TYPES.filter((t) => {
                          if (t.value === 'sentry') return sentryEnabled
                          if (t.value === 's3') return s3Enabled
                          if (t.value === 'axiom') return axiomEnabled
                          if (t.value === 'last9') return last9Enabled
                          return true
                        }).map((type) => (
                          <SelectItem_Shadcn_
                            value={type.value}
                            key={type.value}
                            id={type.value}
                            className="text-left"
                          >
                            {type.name}
                          </SelectItem_Shadcn_>
                        ))}
                      </SelectContent_Shadcn_>
                    </Select_Shadcn_>
                  </FormItemLayout>
                )}
              </div>

              <div className="space-y-8 mt-4">
                {type === 'webhook' && (
                  <>
                    <div className="px-content space-y-8">
                      <LogDrainFormItem
                        value="url"
                        label="Endpoint URL"
                        formControl={form.control}
                        placeholder="https://example.com/log-drain"
                      />
                      <FormField_Shadcn_
                        control={form.control}
                        name="http"
                        render={({ field }) => (
                          <FormItemLayout layout="horizontal" label="HTTP Version">
                            <FormControl_Shadcn_>
                              <RadioGroupCard
                                className="flex gap-2"
                                onValueChange={field.onChange}
                                value={field.value}
                              >
                                <FormItem_Shadcn_ asChild>
                                  <FormControl_Shadcn_>
                                    <RadioGroupCardItem value="http1" label="HTTP/1" />
                                  </FormControl_Shadcn_>
                                </FormItem_Shadcn_>
                                <FormItem_Shadcn_ asChild>
                                  <FormControl_Shadcn_>
                                    <RadioGroupCardItem value="http2" label="HTTP/2" />
                                  </FormControl_Shadcn_>
                                </FormItem_Shadcn_>
                              </RadioGroupCard>
                            </FormControl_Shadcn_>
                          </FormItemLayout>
                        )}
                      />
                    </div>

                    <FormField_Shadcn_
                      control={form.control}
                      name="gzip"
                      render={({ field }) => (
                        <FormItem_Shadcn_ className="space-y-2 px-4">
                          <div className="flex gap-2 items-center">
                            <FormControl_Shadcn_>
                              <Switch checked={field.value} onCheckedChange={field.onChange} />
                            </FormControl_Shadcn_>
                            <FormLabel_Shadcn_ className="text-base">Gzip</FormLabel_Shadcn_>
                            <InfoTooltip align="start">
                              Gzip compresses logs before sending it to the destination.
                            </InfoTooltip>
                          </div>
                        </FormItem_Shadcn_>
                      )}
                    />
                  </>
                )}
                {type === 'datadog' && (
                  <div className="grid gap-4 px-content">
                    <LogDrainFormItem
                      type="password"
                      value="api_key"
                      label="API Key"
                      formControl={form.control}
                      description={
                        <>
                          The API Key obtained from the Datadog dashboard{' '}
                          <a
                            target="_blank"
                            rel="noopener noreferrer"
                            className="text-sm underline transition hover:text-foreground"
                            href="https://app.datadoghq.com/organization-settings/api-keys"
                          >
                            here
                          </a>
                        </>
                      }
                    />
                    <FormField_Shadcn_
                      name="region"
                      control={form.control}
                      render={({ field }) => (
                        <FormItemLayout
                          layout="horizontal"
                          label={'Region'}
                          description={
                            <p>
                              The Datadog region to send logs to. Read more about Datadog regions{' '}
                              <a
                                target="_blank"
                                rel="noopener noreferrer"
                                className="underline hover:text-foreground transition"
                                href="https://docs.datadoghq.com/getting_started/site/#access-the-datadog-site"
                              >
                                here
                              </a>
                              .
                            </p>
                          }
                        >
                          <FormControl_Shadcn_>
                            <Select_Shadcn_ value={field.value} onValueChange={field.onChange}>
                              <SelectTrigger_Shadcn_ className="col-span-3">
                                <SelectValue_Shadcn_ placeholder="Select a region" />
                              </SelectTrigger_Shadcn_>
                              <SelectContent_Shadcn_>
                                <SelectGroup_Shadcn_>
                                  <SelectLabel_Shadcn_>Region</SelectLabel_Shadcn_>
                                  {DATADOG_REGIONS.map((reg) => (
                                    <SelectItem_Shadcn_ key={reg.value} value={reg.value}>
                                      {reg.label}
                                    </SelectItem_Shadcn_>
                                  ))}
                                </SelectGroup_Shadcn_>
                              </SelectContent_Shadcn_>
                            </Select_Shadcn_>
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                  </div>
                )}
                {type === 'loki' && (
                  <div className="grid gap-4 px-content">
                    <LogDrainFormItem
                      type="url"
                      value="url"
                      placeholder="https://my-logs-endpoint.grafana.net/loki/api/v1/push"
                      label="Loki URL"
                      formControl={form.control}
                      description="The Loki HTTP(S) endpoint to send events."
                    />
                    <LogDrainFormItem
                      value="username"
                      label="Username"
                      placeholder="123456789"
                      formControl={form.control}
                    />
                    <LogDrainFormItem
                      type="password"
                      value="password"
                      label="Password"
                      placeholder="glc_ABCD1234567890"
                      formControl={form.control}
                    />
                  </div>
                )}
                {type === 'sentry' && (
                  <div className="grid gap-4 px-content">
                    <LogDrainFormItem
                      type="text"
                      value="dsn"
                      label="DSN"
                      placeholder="https://<project_id>@o<organization_id>.ingest.sentry.io/<project_id>"
                      formControl={form.control}
                      description={
                        <>
                          The DSN obtained from the Sentry dashboard. Read more about DSNs{' '}
                          <a
                            target="_blank"
                            rel="noopener noreferrer"
                            className="text-sm underline transition hover:text-foreground"
                            href="https://docs.sentry.io/concepts/key-terms/dsn-explainer/"
                          >
                            here
                          </a>
                          .
                        </>
                      }
                    />
                  </div>
                )}
                {type === 's3' && (
                  <div className="grid gap-4 px-content">
                    <LogDrainFormItem
                      value="s3_bucket"
                      label="S3 Bucket"
                      placeholder="my-log-bucket"
                      formControl={form.control}
                      description="The name of an existing S3 bucket."
                    />
                    <LogDrainFormItem
                      value="storage_region"
                      label="Region"
                      placeholder="us-east-1"
                      formControl={form.control}
                      description="AWS region where the bucket is located."
                    />
                    <LogDrainFormItem
                      value="access_key_id"
                      label="Access Key ID"
                      placeholder="AKIA..."
                      formControl={form.control}
                    />
                    <LogDrainFormItem
                      type="password"
                      value="secret_access_key"
                      label="Secret Access Key"
                      placeholder="••••••••••••••••"
                      formControl={form.control}
                    />
                    <LogDrainFormItem
                      type="number"
                      value="batch_timeout"
                      label="Batch Timeout (ms)"
                      placeholder="3000"
                      formControl={form.control}
                      description="Recommended 2000–5000ms."
                    />
                    <p className="text-xs text-foreground-lighter">
                      Ensure the account tied to the Access Key ID can write to the specified
                      bucket.
                    </p>
                  </div>
                )}
                {type === 'axiom' && (
                  <div className="grid gap-4 px-content">
                    <LogDrainFormItem
                      type="text"
                      value="dataset_name"
                      label="Dataset name"
                      placeholder="dataset"
                      formControl={form.control}
                      description="Name of the dataset in Axiom where the logs will be sent."
                    />
                    <LogDrainFormItem
                      type="text"
                      value="api_token"
                      label="API Token"
                      placeholder="xaat-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                      formControl={form.control}
                      description="Token allowing ingest access to the specified dataset"
                    />
                  </div>
                )}
                {type === 'last9' && (
                  <div className="grid gap-4 px-content">
                    <FormField_Shadcn_
                      name="region"
                      control={form.control}
                      render={({ field }) => (
                        <FormItemLayout
                          layout="horizontal"
                          label={'Region'}
                          description={
                            <p>
                              The Last9 region to send logs to. Credentials can be obtained from the
                              Last9 OTEL integration panel.
                            </p>
                          }
                        >
                          <FormControl_Shadcn_>
                            <Select_Shadcn_ value={field.value} onValueChange={field.onChange}>
                              <SelectTrigger_Shadcn_ className="col-span-3">
                                <SelectValue_Shadcn_ placeholder="Select a region" />
                              </SelectTrigger_Shadcn_>
                              <SelectContent_Shadcn_>
                                <SelectGroup_Shadcn_>
                                  <SelectLabel_Shadcn_>Region</SelectLabel_Shadcn_>
                                  {LAST9_REGIONS.map((reg) => (
                                    <SelectItem_Shadcn_ key={reg.value} value={reg.value}>
                                      {reg.label}
                                    </SelectItem_Shadcn_>
                                  ))}
                                </SelectGroup_Shadcn_>
                              </SelectContent_Shadcn_>
                            </Select_Shadcn_>
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                    <LogDrainFormItem
                      type="text"
                      value="username"
                      label="Username"
                      placeholder="username"
                      formControl={form.control}
                      description="Username for authentication from Last9 OTEL integration."
                    />
                    <LogDrainFormItem
                      type="password"
                      value="password"
                      label="Password"
                      placeholder="••••••••••••••••"
                      formControl={form.control}
                      description="Password for authentication from Last9 OTEL integration."
                    />
                  </div>
                )}
                <FormMessage_Shadcn_ />
              </div>
            </form>
          </Form_Shadcn_>

          {/* This form needs to be outside the <Form_Shadcn_> */}
          {(type === 'webhook' || type === 'loki') && (
            <>
              <div className="border-t mt-4">
                <div className="px-content pt-2 pb-3 border-b bg-background-alternative-200">
                  <h2 className="text-sm">Custom Headers</h2>
                  <p className="text-xs text-foreground-lighter">
                    {getHeadersSectionDescription()}
                  </p>
                </div>
                <div className="divide-y">
                  {hasHeaders &&
                    Object.keys(headers || {})?.map((headerKey) => (
                      <div
                        className="flex text-sm px-content text-foreground items-center font-mono py-1.5 group"
                        key={headerKey}
                      >
                        <div className="w-full">{headerKey}</div>
                        <div className="w-full truncate" title={headers?.[headerKey]}>
                          {headers?.[headerKey]}
                        </div>
                        <Button
                          className="justify-self-end opacity-0 group-hover:opacity-100 w-7"
                          type="text"
                          title="Remove"
                          icon={<TrashIcon />}
                          onClick={() => removeHeader(headerKey)}
                        ></Button>
                      </div>
                    ))}
                </div>
              </div>
              <form
                onSubmit={(e) => {
                  e.preventDefault()
                  e.stopPropagation()
                  addHeader()
                }}
                className="flex border-t py-4 gap-4 items-center px-content"
              >
                <label className="sr-only" htmlFor="header-name">
                  Header name
                </label>
                <Input_Shadcn_
                  id="header-name"
                  type="text"
                  placeholder="x-header-name"
                  value={newCustomHeader.name}
                  onChange={(e) => setNewCustomHeader({ ...newCustomHeader, name: e.target.value })}
                />
                <label className="sr-only" htmlFor="header-value">
                  Header value
                </label>
                <Input_Shadcn_
                  id="header-value"
                  type="text"
                  placeholder="Header value"
                  value={newCustomHeader.value}
                  onChange={(e) =>
                    setNewCustomHeader({ ...newCustomHeader, value: e.target.value })
                  }
                />

                <Button htmlType="submit" type="outline">
                  Add
                </Button>
              </form>
            </>
          )}
        </SheetSection>

        <div className="mt-auto">
          <SheetSection
            className={cn(
              `border-t bg-background-alternative-200 mt-auto py-1.5 ${!IS_PLATFORM && 'hidden'}`
            )}
          >
            <ul className="text-right text-foreground-light divide-y divide-dashed text-sm">
              <li className="flex items-center justify-between gap-2 py-2" translate="no">
                <span className="text-foreground-lighter">Additional drain cost</span>
                <span className="text-foreground">$60 per month</span>
              </li>
              <li className="flex items-center justify-between gap-2 py-2" translate="no">
                <span className="text-foreground-lighter">Per million events</span>
                <span>+$0.20</span>
              </li>
              <li className="flex items-center justify-between gap-2 py-2" translate="no">
                <span className="text-foreground-lighter">Per GB egress</span>
                <span>+$0.09</span>
              </li>
            </ul>
          </SheetSection>

          <SheetFooter className="p-content !mt-0 !justify-between !flex-row w-full items-center">
            <span className="text-sm text-foreground-light">
              <span>See full pricing breakdown</span>{' '}
              <Link
                href={`${DOCS_URL}/guides/platform/manage-your-usage/log-drains`}
                target="_blank"
                className="text-foreground underline underline-offset-2 decoration-foreground-muted hover:decoration-foreground transition-all"
              >
                here
              </Link>
            </span>
            <Button form={FORM_ID} loading={isLoading} htmlType="submit" type="primary">
              Save destination
            </Button>
          </SheetFooter>
        </div>
      </SheetContent>
    </Sheet>
  )
}

Subdomains

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free