Fix formatting

This commit is contained in:
Owen
2026-03-30 21:35:37 -07:00
parent 45c613dec4
commit a73879ec7a
2 changed files with 212 additions and 45 deletions

View File

@@ -22,7 +22,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { parse } from "zod/v4/core";
const paramsSchema = z const paramsSchema = z
.object({ .object({
@@ -35,10 +35,10 @@ const bodySchema = z.strictObject({
type: z.string().optional(), type: z.string().optional(),
config: z.string().optional(), config: z.string().optional(),
enabled: z.boolean().optional(), enabled: z.boolean().optional(),
sendConnectionLogs: z.boolean().optional().default(false), sendConnectionLogs: z.boolean().optional(),
sendRequestLogs: z.boolean().optional().default(false), sendRequestLogs: z.boolean().optional(),
sendActionLogs: z.boolean().optional().default(false), sendActionLogs: z.boolean().optional(),
sendAccessLogs: z.boolean().optional().default(false) sendAccessLogs: z.boolean().optional()
}); });
export type UpdateEventStreamingDestinationResponse = { export type UpdateEventStreamingDestinationResponse = {
@@ -110,7 +110,19 @@ export async function updateEventStreamingDestination(
); );
} }
const updateData = parsedBody.data; const { type, config, enabled, sendAccessLogs, sendActionLogs, sendConnectionLogs, sendRequestLogs } = parsedBody.data;
const updateData: Record<string, unknown> = {
updatedAt: Date.now()
};
if (type !== undefined) updateData.type = type;
if (config !== undefined) updateData.config = config;
if (enabled !== undefined) updateData.enabled = enabled;
if (sendAccessLogs !== undefined) updateData.sendAccessLogs = sendAccessLogs;
if (sendActionLogs !== undefined) updateData.sendActionLogs = sendActionLogs;
if (sendConnectionLogs !== undefined) updateData.sendConnectionLogs = sendConnectionLogs;
if (sendRequestLogs !== undefined) updateData.sendRequestLogs = sendRequestLogs;
await db await db
.update(eventStreamingDestinations) .update(eventStreamingDestinations)

View File

@@ -27,7 +27,8 @@ import { Switch } from "@app/components/ui/switch";
import { HorizontalTabs } from "@app/components/HorizontalTabs"; import { HorizontalTabs } from "@app/components/HorizontalTabs";
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
import { Textarea } from "@app/components/ui/textarea"; import { Textarea } from "@app/components/ui/textarea";
import { Globe, Plus, Trash2, X } from "lucide-react"; import { Checkbox } from "@app/components/ui/checkbox";
import { Globe, Plus, X } from "lucide-react";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { build } from "@server/build"; import { build } from "@server/build";
@@ -54,6 +55,10 @@ interface Destination {
type: string; type: string;
config: string; config: string;
enabled: boolean; enabled: boolean;
sendAccessLogs: boolean;
sendActionLogs: boolean;
sendConnectionLogs: boolean;
sendRequestLogs: boolean;
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
} }
@@ -215,7 +220,7 @@ function DestinationCard({
{/* Footer: edit button */} {/* Footer: edit button */}
<div className="mt-auto pt-3"> <div className="mt-auto pt-3">
<Button <Button
variant="secondary" variant="outline"
size="sm" size="sm"
onClick={() => onEdit(destination)} onClick={() => onEdit(destination)}
disabled={disabled} disabled={disabled}
@@ -283,10 +288,18 @@ function DestinationModal({
const [deleting, setDeleting] = useState(false); const [deleting, setDeleting] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [cfg, setCfg] = useState<HttpConfig>(defaultConfig()); const [cfg, setCfg] = useState<HttpConfig>(defaultConfig());
const [sendAccessLogs, setSendAccessLogs] = useState(false);
const [sendActionLogs, setSendActionLogs] = useState(false);
const [sendConnectionLogs, setSendConnectionLogs] = useState(false);
const [sendRequestLogs, setSendRequestLogs] = useState(false);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
setCfg(editing ? parseConfig(editing.config) : defaultConfig()); setCfg(editing ? parseConfig(editing.config) : defaultConfig());
setSendAccessLogs(editing?.sendAccessLogs ?? false);
setSendActionLogs(editing?.sendActionLogs ?? false);
setSendConnectionLogs(editing?.sendConnectionLogs ?? false);
setSendRequestLogs(editing?.sendRequestLogs ?? false);
} }
if (!open) { if (!open) {
setDeleteDialogOpen(false); setDeleteDialogOpen(false);
@@ -296,8 +309,27 @@ function DestinationModal({
const update = (patch: Partial<HttpConfig>) => const update = (patch: Partial<HttpConfig>) =>
setCfg((prev) => ({ ...prev, ...patch })); setCfg((prev) => ({ ...prev, ...patch }));
const urlError: string | null = (() => {
const raw = cfg.url.trim();
if (!raw) return null;
try {
const parsed = new URL(raw);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
return "URL must use http or https";
}
if (build === "saas" && parsed.protocol !== "https:") {
return "HTTPS is required on cloud deployments";
}
return null;
} catch {
return "Enter a valid URL (e.g. https://example.com/webhook)";
}
})();
const isValid = const isValid =
cfg.name.trim() !== "" && cfg.url.trim() !== ""; cfg.name.trim() !== "" &&
cfg.url.trim() !== "" &&
urlError === null;
async function handleSave() { async function handleSave() {
if (!isValid) return; if (!isValid) return;
@@ -305,7 +337,11 @@ function DestinationModal({
try { try {
const payload = { const payload = {
type: "http", type: "http",
config: JSON.stringify(cfg) config: JSON.stringify(cfg),
sendAccessLogs,
sendActionLogs,
sendConnectionLogs,
sendRequestLogs
}; };
if (editing) { if (editing) {
await api.post( await api.post(
@@ -383,11 +419,12 @@ function DestinationModal({
items={[ items={[
{ title: "Settings", href: "" }, { title: "Settings", href: "" },
{ title: "Headers", href: "" }, { title: "Headers", href: "" },
{ title: "Body Template", href: "" } { title: "Body Template", href: "" },
{ title: "Logs", href: "" }
]} ]}
> >
{/* ── Settings ─────────────────────────────────── */} {/* ── Settings ─────────────────────────────────── */}
<div className="space-y-5"> <div className="space-y-4 mt-4 p-1">
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label htmlFor="dest-name">Name</Label> <Label htmlFor="dest-name">Name</Label>
<Input <Input
@@ -410,10 +447,22 @@ function DestinationModal({
update({ url: e.target.value }) update({ url: e.target.value })
} }
/> />
{urlError && (
<p className="text-xs text-destructive mt-1">
{urlError}
</p>
)}
</div> </div>
<div className="space-y-2"> <div>
<Label>Authentication</Label> <div className="mb-4">
<label className="font-medium block">
Authentication
</label>
<div className="text-sm text-muted-foreground">
Choose how requests to your endpoint are authenticated.
</div>
</div>
<RadioGroup <RadioGroup
value={cfg.authType} value={cfg.authType}
onValueChange={(v) => onValueChange={(v) =>
@@ -590,27 +639,25 @@ function DestinationModal({
</div> </div>
{/* ── Headers ───────────────────────────────────── */} {/* ── Headers ───────────────────────────────────── */}
<div className="space-y-4"> <div className="space-y-4 mt-4 p-1">
<div> <div>
<p className="text-sm font-medium mb-1"> <div className="mb-4">
Custom HTTP Headers <label className="font-medium block">
</p> Custom HTTP Headers
<p className="text-xs text-muted-foreground mb-4"> </label>
Add custom HTTP headers to every outgoing request. <div className="text-sm text-muted-foreground">
Useful for passing static tokens, setting a custom{" "} Add custom headers to every outgoing request.
<code className="bg-muted px-1 py-0.5 rounded text-xs"> Useful for static tokens or custom{" "}
Content-Type <code className="bg-muted px-1 py-0.5 rounded text-xs">
</code> Content-Type
, or other API requirements. By default, the{" "} </code>
<code className="bg-muted px-1 py-0.5 rounded text-xs"> . By default,{" "}
Content-Type <code className="bg-muted px-1 py-0.5 rounded text-xs">
</code>{" "} Content-Type: application/json
is{" "} </code>{" "}
<code className="bg-muted px-1 py-0.5 rounded text-xs"> is sent.
application/json </div>
</code> </div>
.
</p>
<HeadersEditor <HeadersEditor
headers={cfg.headers} headers={cfg.headers}
onChange={(headers) => update({ headers })} onChange={(headers) => update({ headers })}
@@ -619,19 +666,19 @@ function DestinationModal({
</div> </div>
{/* ── Body Template ─────────────────────────────── */} {/* ── Body Template ─────────────────────────────── */}
<div className="space-y-4"> <div className="space-y-4 mt-4 p-1">
<div> <div className="mb-4">
<p className="text-sm font-medium mb-1"> <label className="font-medium block">
Custom Body Template Custom Body Template
</p> </label>
<p className="text-xs text-muted-foreground mb-4"> <div className="text-sm text-muted-foreground">
Control the structure of the JSON payload sent to your Control the JSON payload structure sent to your
endpoint. If disabled, a default JSON object is sent for endpoint. If disabled, a default JSON object is sent
each event. for each event.
</p> </div>
</div> </div>
<div className="flex items-center gap-3 rounded-md border p-3"> <div className="flex items-center gap-3">
<Switch <Switch
id="use-body-template" id="use-body-template"
checked={cfg.useBodyTemplate} checked={cfg.useBodyTemplate}
@@ -672,6 +719,115 @@ function DestinationModal({
</div> </div>
)} )}
</div> </div>
{/* ── Logs ──────────────────────────────────────── */}
<div className="space-y-4 mt-4 p-1">
<div className="mb-4">
<label className="font-medium block">
Log Types
</label>
<div className="text-sm text-muted-foreground">
Choose which log types are forwarded to this
destination. Only enabled log types will be
streamed.
</div>
</div>
<div className="space-y-3">
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="log-access"
checked={sendAccessLogs}
onCheckedChange={(v) =>
setSendAccessLogs(v === true)
}
className="mt-0.5"
/>
<div>
<label
htmlFor="log-access"
className="text-sm font-medium cursor-pointer"
>
Access Logs
</label>
<p className="text-xs text-muted-foreground mt-0.5">
Resource access attempts, including
authenticated and denied requests.
</p>
</div>
</div>
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="log-action"
checked={sendActionLogs}
onCheckedChange={(v) =>
setSendActionLogs(v === true)
}
className="mt-0.5"
/>
<div>
<label
htmlFor="log-action"
className="text-sm font-medium cursor-pointer"
>
Action Logs
</label>
<p className="text-xs text-muted-foreground mt-0.5">
Administrative actions performed by
users within the organization.
</p>
</div>
</div>
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="log-connection"
checked={sendConnectionLogs}
onCheckedChange={(v) =>
setSendConnectionLogs(v === true)
}
className="mt-0.5"
/>
<div>
<label
htmlFor="log-connection"
className="text-sm font-medium cursor-pointer"
>
Connection Logs
</label>
<p className="text-xs text-muted-foreground mt-0.5">
Site and tunnel connection events,
including connects and disconnects.
</p>
</div>
</div>
<div className="flex items-start gap-3 rounded-md border p-3">
<Checkbox
id="log-request"
checked={sendRequestLogs}
onCheckedChange={(v) =>
setSendRequestLogs(v === true)
}
className="mt-0.5"
/>
<div>
<label
htmlFor="log-request"
className="text-sm font-medium cursor-pointer"
>
Request Logs
</label>
<p className="text-xs text-muted-foreground mt-0.5">
HTTP request logs for proxied
resources, including method, path,
and response code.
</p>
</div>
</div>
</div>
</div>
</HorizontalTabs> </HorizontalTabs>
</CredenzaBody> </CredenzaBody>
@@ -684,7 +840,6 @@ function DestinationModal({
disabled={saving || deleting} disabled={saving || deleting}
className="mr-auto" className="mr-auto"
> >
<Trash2 className="h-4 w-4 mr-1.5" />
Delete Delete
</Button> </Button>
)} )}