diff --git a/messages/en-US.json b/messages/en-US.json index 26fbe5572..f3fd12900 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2971,6 +2971,14 @@ "httpDestAddTitle": "Add HTTP Destination", "httpDestEditDescription": "Update the configuration for this HTTP event streaming destination.", "httpDestAddDescription": "Configure a new HTTP endpoint to receive your organization's events.", + "S3DestEditTitle": "Edit Destination", + "S3DestAddTitle": "Add S3 Destination", + "S3DestEditDescription": "Update the configuration for this S3 event streaming destination.", + "S3DestAddDescription": "Configure a new S3 endpoint to receive your organization's events.", + "datadogDestEditTitle": "Edit Destination", + "datadogDestAddTitle": "Add Datadog Destination", + "datadogDestEditDescription": "Update the configuration for this Datadog event streaming destination.", + "datadogDestAddDescription": "Configure a new Datadog endpoint to receive your organization's events.", "httpDestTabSettings": "Settings", "httpDestTabHeaders": "Headers", "httpDestTabBody": "Body", diff --git a/src/app/[orgId]/settings/logs/streaming/page.tsx b/src/app/[orgId]/settings/logs/streaming/page.tsx index 069059868..022a8eb2e 100644 --- a/src/app/[orgId]/settings/logs/streaming/page.tsx +++ b/src/app/[orgId]/settings/logs/streaming/page.tsx @@ -22,8 +22,7 @@ import { } from "@app/components/Credenza"; import { Button } from "@app/components/ui/button"; import { Switch } from "@app/components/ui/switch"; -import { Globe, MoreHorizontal, Plus, ExternalLink, KeyRound } from "lucide-react"; -import Link from "next/link"; +import { Globe, MoreHorizontal, Plus } from "lucide-react"; import { AxiosResponse } from "axios"; import { build } from "@server/build"; import Image from "next/image"; @@ -39,6 +38,8 @@ import { HttpDestinationCredenza, parseHttpConfig } from "@app/components/HttpDestinationCredenza"; +import { S3DestinationCredenza } from "@app/components/S3DestinationCredenza"; +import { DatadogDestinationCredenza } from "@app/components/DatadogDestinationCredenza"; import { useTranslations } from "next-intl"; // ── Re-export Destination so the rest of the file can use it ────────────────── @@ -182,65 +183,6 @@ interface DestinationTypePickerProps { isPaywalled?: boolean; } -const BOOK_A_DEMO_URL = "https://click.fossorial.io/ep922"; -const CONTACT_URL = "https://pangolin.net/contact"; - -function ContactSalesDialog({ - open, - onOpenChange -}: { - open: boolean; - onOpenChange: (open: boolean) => void; -}) { - const t = useTranslations(); - return ( - - - - {t("streamingAddDestination")} - - -
-
-
- - - Contact sales to enable this feature.{" "} - - Book a demo - - - {" or "} - - contact us - - - . - -
-
-
-
- - - - - -
-
- ); -} - function DestinationTypePicker({ open, onOpenChange, @@ -249,17 +191,6 @@ function DestinationTypePicker({ }: DestinationTypePickerProps) { const t = useTranslations(); const [selected, setSelected] = useState("http"); - const [contactSalesOpen, setContactSalesOpen] = useState(false); - - const ENTERPRISE_ONLY_TYPES: DestinationType[] = ["s3", "datadog"]; - - function handleOptionSelect(type: DestinationType) { - if (ENTERPRISE_ONLY_TYPES.includes(type)) { - setContactSalesOpen(true); - } else { - onSelect(type); - } - } const destinationTypeOptions: ReadonlyArray< StrategyOption @@ -305,11 +236,6 @@ function DestinationTypePicker({ }, [open]); return ( - <> - @@ -329,12 +255,7 @@ function DestinationTypePicker({ { - setSelected(type); - if (ENTERPRISE_ONLY_TYPES.includes(type)) { - setContactSalesOpen(true); - } - }} + onChange={(type) => setSelected(type)} cols={1} /> @@ -344,7 +265,7 @@ function DestinationTypePicker({ + + + + + ); +} diff --git a/src/components/HealthCheckFormFields.tsx b/src/components/HealthCheckFormFields.tsx index ff57fb000..6f5d528db 100644 --- a/src/components/HealthCheckFormFields.tsx +++ b/src/components/HealthCheckFormFields.tsx @@ -1,6 +1,5 @@ "use client"; -import { useState } from "react"; import { UseFormReturn } from "react-hook-form"; import { useTranslations } from "next-intl"; import { Input } from "@/components/ui/input"; @@ -22,78 +21,9 @@ import { FormLabel, FormMessage } from "@/components/ui/form"; -import { - Credenza, - CredenzaBody, - CredenzaClose, - CredenzaContent, - CredenzaFooter, - CredenzaHeader, - CredenzaTitle -} from "@app/components/Credenza"; -import { Button } from "@/components/ui/button"; import { ExternalLink, KeyRound } from "lucide-react"; import Link from "next/link"; -const BOOK_A_DEMO_URL = "https://click.fossorial.io/ep922"; -const CONTACT_URL = "https://pangolin.net/contact"; -const UNIMPLEMENTED_MODES = ["snmp", "icmp"]; - -function ContactSalesDialog({ - open, - onOpenChange -}: { - open: boolean; - onOpenChange: (open: boolean) => void; -}) { - return ( - - - - Coming Soon - - -
-
-
- - - Contact sales to enable this feature.{" "} - - Book a demo - - - {" or "} - - contact us - - - . - -
-
-
-
- - - - - -
-
- ); -} - type HealthCheckFormFieldsProps = { form: UseFormReturn; onFieldChange?: (fieldName: string, value: any) => void; @@ -112,7 +42,6 @@ export function HealthCheckFormFields({ watchedMode }: HealthCheckFormFieldsProps) { const t = useTranslations(); - const [contactSalesOpen, setContactSalesOpen] = useState(false); const showFields = hideEnabledField || watchedEnabled; @@ -121,10 +50,6 @@ export function HealthCheckFormFields({ value: any, fieldOnChange: (v: any) => void ) => { - if (fieldName === "hcMode" && UNIMPLEMENTED_MODES.includes(value)) { - setContactSalesOpen(true); - return; - } fieldOnChange(value); if (onFieldChange) { onFieldChange(fieldName, value); @@ -133,10 +58,6 @@ export function HealthCheckFormFields({ return ( <> - {/* Name */} {showNameField && ( ( - - {t("healthCheckStrategy")} - - {/* Connection fields */} - {watchedMode === "tcp" ? ( -
- ( - - - {t("healthHostname")} - - - - handleChange( - "hcHostname", - e.target.value, - (v) => field.onChange(e) - ) - } - /> - - - - )} - /> - ( - - {t("healthPort")} - - { - const value = - e.target.value; - handleChange( - "hcPort", - value, - field.onChange - ); - }} - /> - - - - )} - /> -
- ) : ( -
- ( - - - {t("healthScheme")} - - - - - )} - /> - ( - - - {t("healthHostname")} - - - - handleChange( - "hcHostname", - e.target.value, - (v) => field.onChange(e) - ) - } - /> - - - - )} - /> - ( - - {t("healthPort")} - - { - const value = - e.target.value; - handleChange( - "hcPort", - value, - field.onChange - ); - }} - /> - - - - )} - /> -
- )} - - {/* HTTP Method + Timeout (shown when not TCP) */} - {watchedMode !== "tcp" && ( -
- ( - - {t("httpMethod")} - - - - )} - /> - ( - - - {t("healthCheckPath")} - - - - handleChange( - "hcPath", - e.target.value, - (v) => field.onChange(e) - ) - } - /> - - - - )} - /> - ( - - - {t("timeoutSeconds")} - - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcTimeout", - value, - field.onChange - ); - }} - /> - - - - )} - /> + contact us + + + . + +
+ )} - {/* TCP timeout (shown only for TCP) */} - {watchedMode === "tcp" && ( - ( - - {t("timeoutSeconds")} - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcTimeout", - value, - field.onChange - ); - }} - /> - - - - )} - /> - )} - - {/* Healthy interval + healthy threshold */} -
- ( - - - {t("healthyIntervalSeconds")} - - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcInterval", - value, - field.onChange - ); - }} - /> - - - - )} - /> - ( - - - {t("healthyThreshold")} - - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcHealthyThreshold", - value, - field.onChange - ); - }} - /> - - - - )} - /> -
- - {/* Unhealthy interval + unhealthy threshold */} -
- ( - - - {t("unhealthyIntervalSeconds")} - - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcUnhealthyInterval", - value, - field.onChange - ); - }} - /> - - - - )} - /> - ( - - - {t("unhealthyThreshold")} - - - { - const value = parseInt( - e.target.value - ); - handleChange( - "hcUnhealthyThreshold", - value, - field.onChange - ); - }} - /> - - - - )} - /> -
- - {/* HTTP-only fields */} - {watchedMode !== "tcp" && ( + {/* Connection fields + all remaining config — hidden for SNMP / ICMP */} + {watchedMode !== "snmp" && watchedMode !== "icmp" && ( <> - {/* Expected Response Codes + TLS Server Name + Follow Redirects */} -
+ {/* Connection fields */} + {watchedMode === "tcp" ? ( +
+ ( + + + {t("healthHostname")} + + + + handleChange( + "hcHostname", + e.target.value, + (v) => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> + ( + + + {t("healthPort")} + + + { + const value = + e.target.value; + handleChange( + "hcPort", + value, + field.onChange + ); + }} + /> + + + + )} + /> +
+ ) : ( +
+ ( + + + {t("healthScheme")} + + + + + )} + /> + ( + + + {t("healthHostname")} + + + + handleChange( + "hcHostname", + e.target.value, + (v) => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> + ( + + + {t("healthPort")} + + + { + const value = + e.target.value; + handleChange( + "hcPort", + value, + field.onChange + ); + }} + /> + + + + )} + /> +
+ )} + + {/* HTTP Method + Path + Timeout (shown when not TCP) */} + {watchedMode !== "tcp" && ( +
+ ( + + + {t("httpMethod")} + + + + + )} + /> + ( + + + {t("healthCheckPath")} + + + + handleChange( + "hcPath", + e.target.value, + (v) => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> + ( + + + {t("timeoutSeconds")} + + + { + const value = + parseInt( + e.target + .value + ); + handleChange( + "hcTimeout", + value, + field.onChange + ); + }} + /> + + + + )} + /> +
+ )} + + {/* TCP timeout (shown only for TCP) */} + {watchedMode === "tcp" && ( ( - {t("expectedResponseCodes")} + {t("timeoutSeconds")} { - const val = - e.target.value; - const value = val - ? parseInt(val) - : null; + const value = parseInt( + e.target.value + ); handleChange( - "hcStatus", + "hcTimeout", + value, + field.onChange + ); + }} + /> + + + + )} + /> + )} + + {/* Healthy interval + healthy threshold */} +
+ ( + + + {t("healthyIntervalSeconds")} + + + { + const value = parseInt( + e.target.value + ); + handleChange( + "hcInterval", value, field.onChange ); @@ -680,25 +537,26 @@ export function HealthCheckFormFields({ /> ( - {t("tlsServerName")} + {t("healthyThreshold")} + onChange={(e) => { + const value = parseInt( + e.target.value + ); handleChange( - "hcTlsServerName", - e.target.value, - (v) => - field.onChange( - e - ) - ) - } + "hcHealthyThreshold", + value, + field.onChange + ); + }} /> @@ -707,60 +565,200 @@ export function HealthCheckFormFields({ />
- {/* Follow Redirects inline toggle */} - ( - - - {t("followRedirects")} - - - - handleChange( - "hcFollowRedirects", - value, - field.onChange - ) - } - /> - - - )} - /> + {/* Unhealthy interval + unhealthy threshold */} +
+ ( + + + {t("unhealthyIntervalSeconds")} + + + { + const value = parseInt( + e.target.value + ); + handleChange( + "hcUnhealthyInterval", + value, + field.onChange + ); + }} + /> + + + + )} + /> + ( + + + {t("unhealthyThreshold")} + + + { + const value = parseInt( + e.target.value + ); + handleChange( + "hcUnhealthyThreshold", + value, + field.onChange + ); + }} + /> + + + + )} + /> +
- {/* Custom Headers */} - ( - - - {t("customHeaders")} - - - - handleChange( - "hcHeaders", - value, - field.onChange - ) - } - rows={4} - /> - - - {t("customHeadersDescription")} - - - - )} - /> + {/* HTTP-only fields */} + {watchedMode !== "tcp" && ( + <> + {/* Expected Response Codes + TLS Server Name */} +
+ ( + + + {t( + "expectedResponseCodes" + )} + + + { + const val = + e.target + .value; + const value = + val + ? parseInt( + val + ) + : null; + handleChange( + "hcStatus", + value, + field.onChange + ); + }} + /> + + + + )} + /> + ( + + + {t("tlsServerName")} + + + + handleChange( + "hcTlsServerName", + e.target + .value, + (v) => + field.onChange( + e + ) + ) + } + /> + + + + )} + /> +
+ + {/* Follow Redirects inline toggle */} + ( + + + {t("followRedirects")} + + + + handleChange( + "hcFollowRedirects", + value, + field.onChange + ) + } + /> + + + )} + /> + + {/* Custom Headers */} + ( + + + {t("customHeaders")} + + + + handleChange( + "hcHeaders", + value, + field.onChange + ) + } + rows={4} + /> + + + {t( + "customHeadersDescription" + )} + + + + )} + /> + + )} )}
diff --git a/src/components/S3DestinationCredenza.tsx b/src/components/S3DestinationCredenza.tsx new file mode 100644 index 000000000..d94293cf0 --- /dev/null +++ b/src/components/S3DestinationCredenza.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Credenza, + CredenzaBody, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import { Button } from "@app/components/ui/button"; +import { Plus, X, KeyRound, ExternalLink } from "lucide-react"; +import Link from "next/link"; +import { useTranslations } from "next-intl"; + +export interface S3DestinationCredenzaProps { + open: boolean; + onOpenChange: (open: boolean) => void; + editing: any; + orgId: string; + onSaved: () => void; +} + +export function S3DestinationCredenza({ + open, + onOpenChange, + editing, + orgId, + onSaved, +}: S3DestinationCredenzaProps) { + const t = useTranslations(); + + return ( + + + + + {editing + ? t("S3DestEditTitle") + : t("S3DestAddTitle")} + + + {editing + ? t("S3DestEditDescription") + : t("S3DestAddDescription")} + + + + +
+
+
+ + + Contact sales to enable this feature.{" "} + + Book a demo + + + {" or "} + + contact us + + + . + +
+
+
+
+ + + + + + +
+
+ ); +}