mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-31 15:06:42 +00:00
Fix formatting
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user