c0b84595a3
## Summary The server half of hosted webhooks: the public ingress endpoint, signature verification, the delivery pipeline (Postgres partitioned storage + ClickHouse for ordering), the in-app partition manager, the HTTP API, and the dashboard (Deliveries, Endpoints, and the in-app test console). The public SDK and docs half is #4537. That PR carries the user-facing API (`webhook()`, `chat.event` / `chat.channels`, the `@trigger.dev/slack` connector) and builds on the shared `@trigger.dev/core` schemas that ship here. ## Shipping behind a flag A `WEBHOOK_ENABLED` env var (default off) gates the public ingress route and the engine worker plus partition cron, so merging and deploying this changes nothing in production until it is flipped on per environment. The dashboard is separately gated per org by the `hasWebhooksAccess` feature flag. ## Note on packages This PR includes the `@trigger.dev/core` schema additions the server compiles against, but carries no changeset. Core is not consumed independently of the SDK, so it is released together with the SDK via #4537. Keeping its changeset off `main` means no release cut from `main` publishes it early.
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { SimpleTooltip } from "~/components/primitives/Tooltip";
|
|
import { useCopy } from "~/hooks/useCopy";
|
|
import { cn } from "~/utils/cn";
|
|
import { Button } from "./Buttons";
|
|
|
|
export function CopyableText({
|
|
value,
|
|
copyValue,
|
|
className,
|
|
asChild,
|
|
variant,
|
|
hideTooltip,
|
|
truncate,
|
|
}: {
|
|
value: string;
|
|
copyValue?: string;
|
|
className?: string;
|
|
asChild?: boolean;
|
|
variant?: "icon-right" | "text-below";
|
|
/**
|
|
* Hide the "Copy"/"Copied" hint tooltip. Use when this is rendered inside another
|
|
* Radix tooltip (e.g. the admin debug panel): the nested tooltip would otherwise
|
|
* fire Radix's global "one tooltip open at a time" close and dismiss the parent.
|
|
*/
|
|
hideTooltip?: boolean;
|
|
/**
|
|
* Ellipsise the value rather than letting it overflow its column. For unbreakable strings
|
|
* (hashes, opaque ids) that offer no wrap opportunity. The copy button moves into a reserved
|
|
* right gutter so it stays visible instead of sitting outside the column.
|
|
*/
|
|
truncate?: boolean;
|
|
}) {
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
const { copy, copied } = useCopy(copyValue ?? value);
|
|
|
|
const resolvedVariant = variant ?? "icon-right";
|
|
|
|
if (resolvedVariant === "icon-right") {
|
|
const iconButton = (
|
|
<span
|
|
className={cn(
|
|
"ml-1 flex size-6 items-center justify-center rounded border border-border-bright bg-background-hover",
|
|
asChild && "p-1",
|
|
copied
|
|
? "text-green-500"
|
|
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright"
|
|
)}
|
|
>
|
|
{copied ? (
|
|
<ClipboardCheckIcon className="size-3.5" />
|
|
) : (
|
|
<ClipboardIcon className="size-3.5" />
|
|
)}
|
|
</span>
|
|
);
|
|
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"group relative inline-flex h-6 items-center",
|
|
truncate && "max-w-full pr-7",
|
|
className
|
|
)}
|
|
onMouseLeave={() => setIsHovered(false)}
|
|
>
|
|
<span
|
|
className={cn(truncate && "min-w-0 truncate")}
|
|
onMouseEnter={() => setIsHovered(true)}
|
|
>
|
|
{value}
|
|
</span>
|
|
<span
|
|
onClick={copy}
|
|
onMouseDown={(e) => e.stopPropagation()}
|
|
className={cn(
|
|
"absolute top-0 z-10 size-6 font-sans",
|
|
// Truncated values reserve a right gutter, so the button sits inside it
|
|
truncate ? "right-0" : "-right-6",
|
|
isHovered ? "flex" : "hidden"
|
|
)}
|
|
>
|
|
{hideTooltip ? (
|
|
iconButton
|
|
) : (
|
|
<SimpleTooltip
|
|
button={iconButton}
|
|
content={copied ? "Copied!" : "Copy"}
|
|
className="font-sans"
|
|
disableHoverableContent
|
|
asChild={asChild}
|
|
/>
|
|
)}
|
|
</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (resolvedVariant === "text-below") {
|
|
return (
|
|
<SimpleTooltip
|
|
button={
|
|
<Button
|
|
variant="minimal/small"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
copy();
|
|
}}
|
|
className={cn(
|
|
"cursor-pointer bg-transparent px-1 py-0 text-left text-text-dimmed transition-colors hover:bg-transparent",
|
|
className
|
|
)}
|
|
>
|
|
<span className="transition-colors group-hover/button:text-text-bright">{value}</span>
|
|
</Button>
|
|
}
|
|
content={copied ? "Copied" : "Copy"}
|
|
className="px-2 py-1 font-sans"
|
|
disableHoverableContent
|
|
open={isHovered || copied}
|
|
onOpenChange={setIsHovered}
|
|
asChild
|
|
/>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|