From dc47b54b1cd4dfbfe580a0e37b138894abdb0669 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 17 May 2026 07:58:12 -0700 Subject: [PATCH] Add cookie editing and inherited request settings --- .../commands/openWorkspaceSettings.tsx | 11 +- .../components/CommandPaletteDialog.tsx | 15 +- apps/yaak-client/components/CookieDialog.tsx | 629 ++++++++++++++++-- .../yaak-client/components/CookieDropdown.tsx | 8 +- .../components/FolderSettingsDialog.tsx | 14 +- .../components/GrpcRequestPane.tsx | 13 +- .../components/HttpRequestPane.tsx | 12 + .../components/ModelSettingsEditor.tsx | 321 +++++++++ apps/yaak-client/components/SelectFile.tsx | 4 +- .../components/Settings/SettingsGeneral.tsx | 300 +++++---- .../components/Settings/SettingsInterface.tsx | 342 +++++----- .../components/Settings/SettingsProxy.tsx | 331 +++++---- .../components/Settings/SettingsTheme.tsx | 181 ++--- .../components/SyncToFilesystemSetting.tsx | 72 +- .../components/WebsocketRequestPane.tsx | 13 +- .../components/WorkspaceEncryptionSetting.tsx | 51 +- .../components/WorkspaceSettingsDialog.tsx | 47 +- apps/yaak-client/components/core/Checkbox.tsx | 8 +- .../components/core/KeyValueRow.tsx | 37 +- .../components/core/PlainInput.tsx | 7 +- apps/yaak-client/components/core/Select.tsx | 10 +- .../yaak-client/components/core/Separator.tsx | 6 +- .../components/core/SettingRow.tsx | 510 ++++++++++++++ .../components/git/GitDropdown.tsx | 2 +- apps/yaak-client/hooks/useAuthTab.tsx | 2 +- crates-cli/yaak-cli/src/plugin_events.rs | 8 +- .../yaak-app-client/src/plugin_events.rs | 7 +- crates-tauri/yaak-app-client/src/ws_ext.rs | 7 +- crates/yaak-git/bindings/gen_models.ts | 189 +++++- crates/yaak-http/src/cookies.rs | 62 +- crates/yaak-http/src/transaction.rs | 200 +++++- crates/yaak-models/bindings/gen_models.ts | 508 ++++++++++++-- ...60302000000_inherited-request-settings.sql | 20 + crates/yaak-models/src/models.rs | 271 +++++++- crates/yaak-models/src/queries/folders.rs | 44 +- .../yaak-models/src/queries/http_requests.rs | 46 +- .../src/queries/websocket_requests.rs | 30 +- crates/yaak-models/src/queries/workspaces.rs | 15 +- crates/yaak-sync/bindings/gen_models.ts | 202 +++++- crates/yaak/src/send.rs | 129 ++-- packages/ui/src/components/Icon.tsx | 10 +- packages/ui/src/components/Table.tsx | 27 +- 42 files changed, 3789 insertions(+), 932 deletions(-) create mode 100644 apps/yaak-client/components/ModelSettingsEditor.tsx create mode 100644 apps/yaak-client/components/core/SettingRow.tsx create mode 100644 crates/yaak-models/migrations/20260302000000_inherited-request-settings.sql diff --git a/apps/yaak-client/commands/openWorkspaceSettings.tsx b/apps/yaak-client/commands/openWorkspaceSettings.tsx index 4f68f87c..1af368f4 100644 --- a/apps/yaak-client/commands/openWorkspaceSettings.tsx +++ b/apps/yaak-client/commands/openWorkspaceSettings.tsx @@ -1,19 +1,10 @@ import type { WorkspaceSettingsTab } from "../components/WorkspaceSettingsDialog"; import { WorkspaceSettingsDialog } from "../components/WorkspaceSettingsDialog"; import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace"; -import { showDialog } from "../lib/dialog"; import { jotaiStore } from "../lib/jotai"; export function openWorkspaceSettings(tab?: WorkspaceSettingsTab) { const workspaceId = jotaiStore.get(activeWorkspaceIdAtom); if (workspaceId == null) return; - showDialog({ - id: "workspace-settings", - size: "md", - className: "h-[calc(100vh-5rem)] !max-h-[40rem]", - noPadding: true, - render: ({ hide }) => ( - - ), - }); + WorkspaceSettingsDialog.show(workspaceId, tab); } diff --git a/apps/yaak-client/components/CommandPaletteDialog.tsx b/apps/yaak-client/components/CommandPaletteDialog.tsx index eec1d3b9..611100a5 100644 --- a/apps/yaak-client/components/CommandPaletteDialog.tsx +++ b/apps/yaak-client/components/CommandPaletteDialog.tsx @@ -15,6 +15,7 @@ import { import { createFolder } from "../commands/commands"; import { createSubEnvironmentAndActivate } from "../commands/createEnvironment"; import { openSettings } from "../commands/openSettings"; +import { openWorkspaceSettings } from "../commands/openWorkspaceSettings"; import { switchWorkspace } from "../commands/switchWorkspace"; import { useActiveCookieJar } from "../hooks/useActiveCookieJar"; import { useActiveEnvironment } from "../hooks/useActiveEnvironment"; @@ -36,7 +37,6 @@ import { appInfo } from "../lib/appInfo"; import { copyToClipboard } from "../lib/copy"; import { createRequestAndNavigate } from "../lib/createRequestAndNavigate"; import { deleteModelWithConfirm } from "../lib/deleteModelWithConfirm"; -import { showDialog } from "../lib/dialog"; import { editEnvironment } from "../lib/editEnvironment"; import { renameModelWithPrompt } from "../lib/renameModelWithPrompt"; import { @@ -99,6 +99,12 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { action: "settings.show", onSelect: () => openSettings.mutate(null), }, + { + key: "workspace_settings.open", + label: "Open Workspace Settings", + action: "workspace_settings.show", + onSelect: () => openWorkspaceSettings(), + }, { key: "app.create", label: "Create Workspace", @@ -128,12 +134,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { key: "cookies.show", label: "Show Cookies", onSelect: async () => { - showDialog({ - id: "cookies", - title: "Manage Cookies", - size: "full", - render: () => , - }); + CookieDialog.show(activeCookieJar?.id ?? null); }, }, { diff --git a/apps/yaak-client/components/CookieDialog.tsx b/apps/yaak-client/components/CookieDialog.tsx index 6c8062c5..b21c813b 100644 --- a/apps/yaak-client/components/CookieDialog.tsx +++ b/apps/yaak-client/components/CookieDialog.tsx @@ -1,9 +1,30 @@ import type { Cookie } from "@yaakapp-internal/models"; import { cookieJarsAtom, patchModel } from "@yaakapp-internal/models"; +import { formatDate } from "date-fns/format"; import { useAtomValue } from "jotai"; +import { type ComponentProps, useMemo, useState } from "react"; +import { showDialog } from "../lib/dialog"; +import { jotaiStore } from "../lib/jotai"; import { cookieDomain } from "../lib/model_util"; -import { Banner, InlineCode } from "@yaakapp-internal/ui"; +import { + Icon, + SplitLayout, + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + TruncatedWideTableCell, +} from "@yaakapp-internal/ui"; import { IconButton } from "./core/IconButton"; +import { Checkbox } from "./core/Checkbox"; +import classNames from "classnames"; +import { EventDetailHeader } from "./core/EventViewer"; +import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow"; +import { EmptyStateText } from "./EmptyStateText"; +import { PlainInput } from "./core/PlainInput"; +import { showAlert } from "../lib/alert"; interface Props { cookieJarId: string | null; @@ -12,56 +33,574 @@ interface Props { export const CookieDialog = ({ cookieJarId }: Props) => { const cookieJars = useAtomValue(cookieJarsAtom); const cookieJar = cookieJars?.find((c) => c.id === cookieJarId); + const [filter, setFilter] = useState(""); + const [filterUpdateKey, setFilterUpdateKey] = useState(0); + const [selectedCookieKey, setSelectedCookieKey] = useState(null); + const [editingCookieKey, setEditingCookieKey] = useState(null); + const [draftCookie, setDraftCookie] = useState(null); + const filteredCookies = useMemo(() => { + return cookieJar?.cookies.filter((cookie) => cookieMatchesFilter(cookie, filter)) ?? []; + }, [cookieJar?.cookies, filter]); + const selectedCookie = useMemo( + () => + selectedCookieKey == null + ? null + : (filteredCookies.find((cookie) => cookieKey(cookie) === selectedCookieKey) ?? null), + [filteredCookies, selectedCookieKey], + ); + const detailCookie = draftCookie ?? selectedCookie; + const isCreatingCookie = editingCookieKey === NEW_COOKIE_KEY; + const isEditingCookie = draftCookie != null; + + const handleAddCookie = () => { + setSelectedCookieKey(null); + setEditingCookieKey(NEW_COOKIE_KEY); + setDraftCookie(newCookieDraft()); + }; + + const handleEditCookie = () => { + if (selectedCookie == null) { + return; + } + + setEditingCookieKey(cookieKey(selectedCookie)); + setDraftCookie(selectedCookie); + }; + + const handleCancelEdit = () => { + if (isCreatingCookie) { + setSelectedCookieKey(null); + } + setEditingCookieKey(null); + setDraftCookie(null); + }; + + const handleCloseDetails = () => { + if (isEditingCookie) { + handleCancelEdit(); + return; + } + + setSelectedCookieKey(null); + }; + + const handleSaveCookie = () => { + if (cookieJar == null || draftCookie == null) { + return; + } + + const nextCookie = normalizeCookie(draftCookie); + if (nextCookie.name.trim().length === 0) { + showAlert({ + id: "invalid-cookie-name", + title: "Invalid Cookie", + body: "Cookie name is required.", + }); + return; + } + + const nextCookieKey = cookieKey(nextCookie); + const nextCookies = cookieJar.cookies.filter((cookie) => { + const key = cookieKey(cookie); + if (editingCookieKey != null && key === editingCookieKey) { + return false; + } + return key !== nextCookieKey; + }); + + patchModel(cookieJar, { cookies: [...nextCookies, nextCookie] }); + setSelectedCookieKey(nextCookieKey); + setEditingCookieKey(null); + setDraftCookie(null); + }; if (cookieJar == null) { return
No cookie jar selected
; } - if (cookieJar.cookies.length === 0) { - return ( - - Cookies will appear when a response contains the Set-Cookie header - - ); - } - return ( -
- - - - - - - - - {cookieJar?.cookies.map((c: Cookie) => ( - - - - - - ))} - -
DomainCookie -
- {cookieDomain(c)} - - {c.raw_cookie} - - - patchModel(cookieJar, { - cookies: cookieJar.cookies.filter((c2: Cookie) => c2 !== c), - }) - } - /> -
+
+
+ 0 && ( + { + setFilter(""); + setFilterUpdateKey((key) => key + 1); + }} + /> + ) + } + /> + +
+ {cookieJar.cookies.length === 0 && detailCookie == null ? ( + + Cookies will appear when a response includes a Set-Cookie header. + + ) : filteredCookies.length === 0 && detailCookie == null ? ( + No cookies match the current filter. + ) : ( + + filteredCookies.length === 0 ? ( +
+ No cookies match the current filter. +
+ ) : ( + + + + Name + Value + Domain + Path + Expires + Size + HTTP Only + Secure + Same Site + + { + setSelectedCookieKey(null); + setEditingCookieKey(null); + setDraftCookie(null); + patchModel(cookieJar, { cookies: [] }); + }} + /> + + + + + {filteredCookies.map((c: Cookie) => { + const key = cookieKey(c); + const isSelected = key === selectedCookieKey; + + return ( + { + setSelectedCookieKey(key); + setEditingCookieKey(null); + setDraftCookie(null); + }} + > + {c.name} + + {c.value} + + {cookieDomain(c)} + {c.path} + {cookieExpires(c)} + {cookieSize(c)} + + + + + + + {c.sameSite} + + { + event.stopPropagation(); + if (isSelected) { + setSelectedCookieKey(null); + } + if (editingCookieKey === key) { + setEditingCookieKey(null); + setDraftCookie(null); + } + patchModel(cookieJar, { + cookies: cookieJar.cookies.filter( + (c2: Cookie) => cookieKey(c2) !== key, + ), + }); + }} + /> + + + ); + })} + +
+ ) + } + secondSlot={ + detailCookie == null + ? null + : ({ style }) => ( +
+ + {isEditingCookie ? ( + + ) : ( + + )} +
+ ) + } + /> + )}
); }; + +CookieDialog.show = (cookieJarId: string | null) => { + const cookieJar = jotaiStore.get(cookieJarsAtom)?.find((jar) => jar.id === cookieJarId); + if (cookieJar == null) { + showAlert({ + id: "invalid-jar", + body: `Failed to find cookie jar for ID: ${cookieJarId}`, + title: "Invalid Cookie Jar", + }); + return; + } + + showDialog({ + id: "cookies", + title: `${cookieJar.name} Cookies`, + size: "full", + render: () => , + }); +}; + +function CookieDetails({ cookie }: { cookie: Cookie }) { + return ( +
+ + {cookie.name} + +
{cookie.value}
+
+ {cookieDomain(cookie)} + {cookie.path} + {cookieExpires(cookie)} + {cookieSize(cookie)} + {cookie.httpOnly ? "Yes" : "No"} + {cookie.secure ? "Yes" : "No"} + {cookie.sameSite && ( + {cookie.sameSite} + )} +
+
+ ); +} + +function CookieEditor({ + cookie, + onChange, +}: { + cookie: Cookie; + onChange: (cookie: Cookie) => void; +}) { + const sessionCookie = cookie.expires === "SessionEnd"; + + return ( +
+ + + onChange({ ...cookie, name })} + /> + + + onChange({ ...cookie, value })} + /> + + + onChange(cookieWithDomain(cookie, domain))} + /> + + + onChange({ ...cookie, path })} + /> + + +
+ + onChange({ + ...cookie, + expires: checked + ? "SessionEnd" + : cookieExpiresFromInput(defaultCookieExpiresInputValue()), + }) + } + /> + onChange({ ...cookie, expires: cookieExpiresFromInput(value) })} + /> +
+
+ {cookieSize(cookie)} + + onChange({ ...cookie, httpOnly })} + /> + + + onChange({ ...cookie, secure })} + /> + + + + +
+
+ ); +} + +function CookieKeyValueRow({ labelClassName, ...props }: ComponentProps) { + return ( + + ); +} + +function CookieTextInput({ + autoFocus, + disabled, + onChange, + placeholder, + required, + value, +}: { + autoFocus?: boolean; + disabled?: boolean; + onChange: (value: string) => void; + placeholder?: string; + required?: boolean; + value: string; +}) { + return ( + onChange(event.target.value)} + placeholder={placeholder} + required={required} + type="text" + value={value} + /> + ); +} + +function CookieTextarea({ onChange, value }: { onChange: (value: string) => void; value: string }) { + return ( +