Compare commits

...

270 Commits

Author SHA1 Message Date
naman-bruno
715b6ecbb0 fix: normalize Windows paths for cross-platform compatibility (#7185)
* fix: normalize Windows paths for cross-platform compatibility in workspace

* fixes
2026-02-18 17:41:52 +05:30
lohit
a036396cb8 fix: isJson assertion fails after res.setBody() with object in node-vm (#7191)
* fix: isJson assertion fails after res.setBody() with object in node-vm

Objects created inside Node's vm.createContext() have a different Object
constructor than the host realm. When res.setBody() is called with a JS
object from a script, _.cloneDeep preserves the cross-realm prototype,
causing obj.constructor === Object to fail in the isJson assertion.

Replace with Object.prototype.toString.call() which is cross-realm safe.

* fix: register isJson chai assertion in QuickJS test runtime

The bundled chai in QuickJS only exposes { expect, assert } via
requireObject — no Assertion class. Access the prototype through
Object.getPrototypeOf(expect(null)) and use Object.defineProperty
to register the json property directly.

* fix: enable assertion chaining on isJson in QuickJS runtime

The QuickJS isJson property getter was missing `return this`, preventing
chai assertion chaining (e.g. expect(body).to.be.json.and...).
2026-02-18 17:35:06 +05:30
naman-bruno
db612679d6 fix: update protobuf and import path handling in opencollection (#7166) 2026-02-17 15:19:37 +05:30
Sid
ec9a03f208 tests: fix breaking tests (#7132)
* fix: update placeholder text for environment variable input

* fix: handle undefined color in environment objects

Don't export if `undefined`

* fix: update collection import logic for YML and BRU formats

* fix: ensure error icon is not visible after header validation

* fix: specify format for collection and environment serialization
2026-02-13 19:15:33 +05:30
Pooja
1448fe4b52 fix: env draft loss on color change and rename (#7130) 2026-02-13 19:15:20 +05:30
Pooja
c957c9371d fix: openapi content level example (#7091)
* fix: openapi content level example

* add: unit tests
2026-02-13 19:12:13 +05:30
lohit
811daec92c fix(node-vm): scripting context and module resolution (#7033)
* fix(node-vm): scripting context and module resolution issues

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): use vm.createContext for true isolation and fix prototype mismatches

- Replace vm.compileFunction with vm.createContext + runInContext for true isolation
- Remove ECMAScript built-ins from safeGlobals (VM provides its own versions)
- This fixes prototype chain mismatches that broke libraries like @faker-js/faker
- Add sanitized process object (allows env, blocks exit/kill)
- Add global/globalThis pointing to isolated context (not host)
- Extract safe globals to constants.js for maintainability
- Remove typed-arrays mixin (VM provides TypedArrays)
- Add comprehensive isolation tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): remove process, add Error types and TypedArrays mixin, add jose test

- Remove process object from script context (security hardening)
- Remove createSanitizedProcess function from constants.js
- Add Error types to safeGlobals for instanceof checks with host errors
- Add TypedArrays mixin for host API compatibility (TextEncoder, crypto, Buffer)
- Add jose library and test for JWT sign/verify functionality
- Update tests to reflect process removal

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): handle circular dependencies and failed module caching

- Pre-populate module cache before execution to support circular requires
- Cache moduleObj instead of moduleObj.exports to handle module.exports reassignment
- Remove failed modules from cache to allow retry
- Add test for circular dependency handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): spread all context properties in buildScriptContext

Instead of explicitly listing each context property, spread all
properties from the context input to support future additions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): add filtered process object to script context

Expose a sanitized process object with only safe read-only properties
(argv, version, arch, platform, pid, features) while keeping env empty
for security.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(node-vm): add comprehensive tests for Node.js builtins

Add 18 test files for Node.js builtin APIs in developer sandbox mode:
- Buffer, URL, TextEncoder/TextDecoder, btoa/atob
- Web Crypto API and node:crypto module
- Timers (setTimeout, setInterval, setImmediate, queueMicrotask)
- Fetch API (Request, Response, Headers, FormData, Blob)
- Intl formatters, JSON, Events (Event, EventTarget, CustomEvent)
- Node modules: fs, path, os, util, stream, zlib, querystring

All tests skip in safe mode using bru.runner.skipRequest().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(node-vm): address CodeRabbit review feedback

- Block absolute paths from bypassing security by routing through loadLocalModule
- Fix process tests to expect sanitized object instead of undefined
- Fix cache test to verify module executes only once
- Add tests for absolute path handling (block outside, allow within roots)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: lint issues

* fix(node-vm): recontextualize host objects for cross-context deep equality

Objects passed from the host context into the Node VM have different
Object/Array constructors than objects created inside the VM. This breaks
deep equality checks in libraries like AJV, where fast-deep-equal fails
on `a.constructor !== b.constructor` for structurally identical objects.

Add recontextualizeScript to utils.js that wraps getter methods (res.getBody,
res.getHeaders, req.getBody, req.getHeaders, req.getPathParams, req.getTags,
bru.getVar) to JSON round-trip returned objects inside the VM, giving them
VM-native prototypes.

Add external-lib-with-bru-req-res-objects package and tests to verify
bru/req/res accessibility from npm modules. Update ajv.bru tests to
validate res.getBody() against AJV schemas with enum on nested objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(node-vm): update spec to use saved mock refs after recontextualize

The recontextualizeScript wraps res.getBody with a JSON round-trip
function, replacing the jest mock on the context object. Save mock
references before calling runScriptInNodeVm so assertions work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(node-vm): shallow-copy mutable process properties in sandbox

process.argv, process.versions, and process.features were passed by
reference, allowing sandboxed scripts to mutate the host process.
Shallow-copy these properties to prevent leaking mutable references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(node-vm): use recursive clone in toVMNative instead of JSON round-trip

JSON.stringify converts undefined to null in arrays, breaking tests like
res.setBody([..., undefined, ...]). Replace with recursive clone that
creates new VM-native objects/arrays while preserving undefined values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(node-vm): generalize recontextualize to wrap all bru/req/res methods

Instead of hardcoding specific method names, walk the prototype chain
with Object.getOwnPropertyNames to discover and wrap all methods that
return Objects/Arrays. Async methods (sendRequest, runRequest) get their
resolved values wrapped. The res callable and res.body/res.headers are
also recontextualized for direct access and query usage.

Adds integration tests for VM-native prototype checks across res, req,
bru APIs, res() callable queries, and bru.sendRequest patterns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* revert(node-vm): remove recontextualizeScript and related tests

The recontextualize approach of wrapping all bru/req/res methods
to return VM-native objects is being reverted in favor of a
different solution to the cross-context prototype mismatch issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(node-vm): expose full process object in developer sandbox via safeGlobals

* test(node-vm): update process tests for full process object in developer sandbox

* test(node-vm): update spec to verify process.nextTick availability

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 01:22:59 +05:30
Abhishek S Lal
282b0bbae7 fix: enhance tag handling and validation in collection import/export (#7107)
- Added collection format handling in Tags component.
- Updated convertCollection function to accept collectionFormat parameter.
- Improved tag validation logic in TagList component based on collection format.
- Adjusted OpenAPI transformation functions to support collection format options.
- Enhanced schema validation for tags to allow spaces and underscores.
2026-02-12 00:43:11 +05:30
Bijin A B
68334b362f fix(save-all): fix save all modified requests while closing the app (#7118) 2026-02-12 00:08:33 +05:30
Abhishek S Lal
659e6e0293 fix: enhance HTTP response status validation in stringifyHttpRequest function (#7117)
Updated the response status handling to ensure it is a positive integer before assignment, improving data integrity in HTTP request stringification.
2026-02-11 21:19:21 +05:30
Chirag Chandrashekhar
d3337c8e9e Fix/save transient request new folder theme match (#7116)
* fix: match filesystem name input style to NewFolder modal in SaveTransientRequest

- Update label to match NewFolder format with '(on filesystem)' suffix
- Add folder icon before the input field
- Apply PathDisplay-like styling with yellow text color and monospace font
- Use matching background, border, and padding from PathDisplay component

* fix: add edit toggle and help tooltip to SaveTransientRequest filesystem name

- Add edit/display mode toggle matching NewFolder modal behavior
- Show PathDisplay when not editing, input field when editing
- Add Help tooltip with placement support for filesystem name field
- Add placement prop to Help component (top, bottom, left, right)
- Remove unused filesystem input styles from StyledWrapper

* fix: update Help component usage in SaveTransientRequest filesystem name field

- Change Help component width prop from a string to a number for consistency.
2026-02-11 21:19:10 +05:30
naman-bruno
54488d6d06 fix: collection zip import for default workspace (#7108)
* fix: collection zip import for default workspace

* fixes
2026-02-11 21:18:47 +05:30
lohit
6d646e3cef fix: pass app-level proxy config to bru.sendRequest (#7113)
When collection proxy is set to "inherit", bru.sendRequest was skipping
the app-level proxy and falling through directly to system proxy. Now it
correctly checks app-level proxy settings first, matching the behavior
of normal requests. When appLevelProxyConfig is not provided (e.g. CLI),
falls through to system proxy preserving existing behavior.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:09:32 +05:30
gopu-bruno
78af8be59e fix: update codemirror bg for pastel light theme (#7110) 2026-02-11 18:32:26 +05:30
Chirag Chandrashekhar
db137da8ed fix: match filesystem name input style to NewFolder modal in SaveTransientRequest (#7109)
- Update label to match NewFolder format with '(on filesystem)' suffix
- Add folder icon before the input field
- Apply PathDisplay-like styling with yellow text color and monospace font
- Use matching background, border, and padding from PathDisplay component
2026-02-11 18:32:18 +05:30
Pooja
cb3f6629bb fix: persist environment color on import/export (#7045) 2026-02-11 18:31:46 +05:30
naman-bruno
0ba6c3d132 fix: filter existing paths for apispec in workspace (#7104) 2026-02-11 18:31:31 +05:30
Sid
1f65387ea8 fix: improve environment variable comparison by stripping UIDs (#7100) 2026-02-11 18:31:22 +05:30
Pooja
9acfed63c5 fix: env color picker ui (#7096)
* fix: env color picker ui

* rm: toast for color change

* fix: color slider alignment
2026-02-11 18:31:11 +05:30
Sid
7bc0c1b967 fix: improve value handling in editor components (#7098) 2026-02-11 18:30:59 +05:30
lohit
44538be00b fix: mark Node.js built-in modules as external in rollup config (#7095)
Use `isBuiltin` from the `module` package to dynamically exclude all
Node.js built-in modules from the bundle, preventing rollup from
trying to bundle core modules like path, fs, crypto, etc.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:30:48 +05:30
naman-bruno
01e3999631 handle unsaved changes in dot env file editor (#7094)
* handle unsaved changes in dot env file editor

* fixes
2026-02-11 18:30:40 +05:30
Chirag Chandrashekhar
459620170a fix: validate folder and file names in SaveTransientRequest component (#7060)
- Added validation for folder and file names to ensure they are not empty and conform to naming rules.
- Display error messages using toast notifications for invalid names.
2026-02-11 18:30:31 +05:30
gopu-bruno
b6e455f41b fix: disable text-overflow ellipsis on checkbox column (#7080) 2026-02-11 18:28:35 +05:30
naman-bruno
0f017b122c feat: validate ZIP file format for collections before import (#7085) 2026-02-11 18:28:25 +05:30
Pooja
aba8e14377 fix: header and var tooltip overflow (#7082) 2026-02-11 18:28:11 +05:30
naman-bruno
46bc0ffce7 feat: add ZIP file import for collections (#7063)
* feat: add ZIP file import for collections
2026-02-11 18:27:54 +05:30
Chirag Chandrashekhar
3080c3e144 feat: implement filtering of transient items across collection operations (#7062)
- Added `filterTransientItems` utility to recursively remove transient items from collections.
- Updated export functions for OpenCollection and Postman to filter out transient items before export.
- Enhanced collection handling in various components to skip transient requests during processing.
- Adjusted RunConfigurationPanel to exclude transient items from request handling.
2026-02-11 18:27:33 +05:30
naman-bruno
4a0000e10f Merge pull request #7067 from naman-bruno/fix/import-tests
fix: import tests
2026-02-11 18:27:06 +05:30
Chirag Chandrashekhar
838e29682d feat: enhance SaveTransientRequest component with folder navigation and input handling improvements (#7061) 2026-02-11 18:24:44 +05:30
Chirag Chandrashekhar
266e9ce230 bugfix: auto open saved transient request (#7058) 2026-02-11 18:24:32 +05:30
Chirag Chandrashekhar
977a48dfa7 fix: update dependency in CreateTransientRequest to include collectionUid in useMemo dependencies (#7057) 2026-02-11 18:24:15 +05:30
Chirag Chandrashekhar
777669ba65 fix: improve error handling in CreateTransientRequest and SaveTransientRequest components (#7059) 2026-02-11 18:24:03 +05:30
Chirag Chandrashekhar
ca86824bb9 Bugfix/close saved deleting collections (#7048) 2026-02-11 18:23:30 +05:30
Pooja
12a45cbd82 fix: code editor null value crash (#7039) 2026-02-11 18:23:06 +05:30
Pooja
b1f83f2ab1 fix: add missing URL helper translations for Bruno to Postman export (#7026)
* fix: add missing URL helper translations for Bruno to Postman export

* fix : comment
2026-02-05 18:36:51 +05:30
naman-bruno
6e6804055d fix: default format on import modal (#7017) 2026-02-02 21:31:01 +05:30
naman-bruno
5904c36cdb feat: enhance ShareCollection component with export options and UI improvements (#7016) 2026-02-02 21:01:03 +05:30
naman-bruno
8c997c46af make yml default option (#6985)
* make yml default option
2026-02-02 19:45:45 +05:30
naman-bruno
700e25a1d5 Add: dotenv visual editor (#6964) 2026-02-02 19:43:54 +05:30
naman-bruno
c9059c9905 refactor: update opencollection extension for bruno (#7013)
* refactor: update YML parsing and stringification to utilize 'bruno' extensions for ignore and presets

* fix
2026-02-02 19:35:17 +05:30
naman-bruno
416b693afc fix: YML parsing and stringification to support post-response variables (#7009) 2026-02-02 18:57:35 +05:30
lohit
bafb235e72 feat: add certs and proxy config to bru.sendRequest API (#6988)
* feat: add certs and proxy config to bru.sendRequest API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: handle URL string argument in bru.sendRequest

When bru.sendRequest is called with a plain URL string instead of a
config object, the function now normalizes it to { url: string } before
processing. This fixes the case where spreading a string created an
invalid config object.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add variable interpolation to bru.sendRequest certs and proxy config

Interpolate environment variables in clientCertificates and proxy
configuration for bru.sendRequest API, enabling use of variables like
{{CERT_PATH}} or {{PROXY_HOST}} in certificate paths and proxy settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: use interpolateObject for certs and proxy config interpolation

- Add interpolateObject to electron's interpolate-string.js using
  buildCombinedVars pattern (matches CLI implementation)
- Simplify cert-utils.js by using interpolateObject instead of
  manual field-by-field interpolation
- Add interpolation for clientCertificates and proxy config in CLI's
  run-single-request.js for bru.sendRequest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: add all variable types to sendRequest interpolation options

- Add globalEnvVars, collectionVariables, folderVariables, requestVariables
  to sendRequestInterpolationOptions for complete variable support
- Use cached system proxy instead of redundant getSystemProxy() call
- Remove duplicate getOptions() call

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: skip CA cert loading when TLS verification is disabled

Only load CA certificates when shouldVerifyTls is true, since they
are not used for validation when TLS verification is disabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:59:46 +05:30
Sid
06dd5c14d5 CI: flaky test monitor (#7007) 2026-02-02 17:16:27 +05:30
Sanjai Kumar
0f0c2b5912 Revert "fix: ephemeral environment variables being saved to filesystem (#6723)" (#7012)
This reverts commit 5b1b1b5541.
2026-02-02 16:50:06 +05:30
gopu-bruno
6664295b2b fix: update sidebar item copy toast message (#7011) 2026-02-02 16:29:50 +05:30
shubh-bruno
679cb91549 fix: refocus search bar in code editor on Ctrl/Cmd + F (#6980) 2026-02-02 15:53:32 +05:30
naman-bruno
b9d9a27599 add support for additional context roots in opencollection (#6995)
* add support for additional context roots in opencollection
2026-02-02 14:04:46 +05:30
Chirag Chandrashekhar
1fc703e4e3 feat: implement dynamic terminal theming based on app theme (#6812)
- Added a function to build terminal themes from the app's current theme.
- Updated terminal creation and rendering functions to accept and apply the dynamic theme.
- Implemented a useEffect hook to update terminal themes when the app theme changes.
2026-02-02 12:38:51 +05:30
anusree-bruno
89a0494e7e Feat/preferences UI polish (#6989)
* Preferences UI polish

* chore: cleanup

* chore: cleanup

* chore: removed unused classname
2026-02-02 10:34:32 +05:30
shubh-bruno
04806144a5 fix: response pane actions for GQL requests (#6911) 2026-01-31 09:30:07 +05:30
Pooja
0c3d20b198 fix: restore cursor focus on save and show placeholder for empty cells (#6795) 2026-01-31 09:08:42 +05:30
gopu-bruno
3ddf8e2a8b fix: support multiline descriptions in example blocks (#6879)
* fix: support multiline descriptions in example blocks

* refactor: use outdentString for example multiline text block parsing

* test: add test case for examples without description field

* test: add jsonToBru conversion test for multiline descriptions

* refactor: generalize descriptionvalue to textvalue in example grammar
2026-01-30 23:04:48 +05:30
gopu-bruno
f10422cca6 fix: support multiline example names (#6895)
* fix: support mutliline example names

* fix: improve multiline example name parsing and processing

* test: add test cases for example name field parsing

* refactor: simplify example name parsing

* fix: sanitize multiline example names in Postman imports

* fix: sanitize Postman example names on import

* fix: sanitize OpenAPI example names on import
2026-01-30 23:01:28 +05:30
naman-bruno
ba166561cc feat: add custom AppMenu component for windows & linux (#6934)
* feat: add custom AppMenu component for windows & linux

* fixes

* fixes

* fixes

* fixes
2026-01-30 22:58:36 +05:30
lohit
3112380289 feat: cache system proxy to avoid redundant lookups (#6990)
- bruno-cli: fetch system proxy once before request loop and store in options
- bruno-electron: initialize system proxy cache at app startup
- Add refresh button in preferences to manually update cached system proxy
- Replace per-request getSystemProxy() calls with cached values

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:15:36 +05:30
Sid
559946bcce feat: add search functionality to environment variables. (#6659) (#6966) 2026-01-30 19:04:16 +05:30
Pooja
e1c01ebe18 feat: add resizable columns to table (#6843) 2026-01-30 18:25:13 +05:30
Sid
eb5dc12b43 Merge pull request #6970 from usebruno/feature/environment-color-extended
feat(#304) Environments color 🎨 (#1053)
2026-01-30 17:12:35 +05:30
Pooja
a04ff3e819 feat(#304) Environments color 🎨 (#1053) (#6974) 2026-01-30 17:07:13 +05:30
Mathieu DREANO
5a6714f085 feat(#304) Environments color 🎨 (#1053)
* associate environment to a color

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

use StyledWrapper

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

don't save anything for color if it is not set

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

use redux store instead of local state

remove logs

fix selectedEnvironment

cleanup

add bottom border on active tab

* associate environment to a color

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* move dependency to appropriate package.json

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* use border instead of background color

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* simplify onColorChange

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* add black, keep backgound on unselected color

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* fix conflicts

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

* associate environment to a color

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

use StyledWrapper

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

don't save anything for color if it is not set

Signed-off-by: mathieu <mathieu.dreano@gmail.com>

use redux store instead of local state

remove logs

fix selectedEnvironment

cleanup

add bottom border on active tab

# Conflicts:
#	packages/bruno-app/src/components/Environments/EnvironmentSelector/StyledWrapper.js
#	packages/bruno-app/src/components/Environments/EnvironmentSelector/index.js
#	packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
#	packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js
#	packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js
#	packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js

* Update packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentColor/index.js

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* unused selectedEnvironment prop in EnvironmentList

Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com>

* RequestTab, avoid unnecessary call if undefined activeCollection

Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com>

* use @uiw/reac-color instead of react-color

Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com>

---------

Signed-off-by: mathieu <mathieu.dreano@gmail.com>
Signed-off-by: Mathieu D <mathieu.dreano@decathlon.com>
Co-authored-by: Mathieu D <mathieu.dreano@decathlon.com>
Co-authored-by: Anoop M D <anoop@usebruno.com>
Co-authored-by: Mathieu DREANO <122891400+mdreano@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-30 17:07:13 +05:30
lohit
214e1434e5 fix: ensure app gains focus when restoring main window (#6984)
Add app.focus({ steal: true }) before restoring the window to ensure
the application properly gains focus when activated.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:52:30 +05:30
naman-bruno
3996b86bcb fix: update certificate type handling in stringifyCollection function (#6986) 2026-01-30 15:51:04 +05:30
sanish chirayath
8fface4bbe feat: enhance request and response translation capabilities (#6981)
- Added translations for req.method, req.headers, and req.body to their Postman equivalents.
- Implemented handling for req.setUrl, req.setMethod, req.setBody, and req.setHeaders, converting them to appropriate Postman assignments and method calls.
- Introduced support for translating res.responseTime and res.headers properties.
- Updated tests to cover new translations and ensure accuracy in request and response handling.
2026-01-30 15:50:27 +05:30
naman-bruno
b268aa9f98 fix: global environment flag for cli (#6969)
* fix: global environment flag for cli

* fixes
2026-01-30 15:37:56 +05:30
Pooja
df3ff5e48a fix: refresh scopeInfo after variable save in tooltip editor (#6935) 2026-01-30 13:33:30 +05:30
Pooja
6ca5c71f7a fix: cURL auth import for digest and ntlm (#6292) 2026-01-30 13:05:28 +05:30
Chirag Chandrashekhar
ca4d0dd40b Feature/transient request (#6878)
* feat: add functionality to retrieve collection path from transient file requests in IPC module

* feat: implement transient directory handling in collection mounting process

* add action to store transient directory paths in Redux state
* update IPC handler to create and return a temporary directory for collections
* modify collection mount action to dispatch transient directory addition

* feat: add CreateTransientRequest component for managing transient requests

* implement CreateTransientRequest component to facilitate the creation of HTTP, GraphQL, gRPC, and WebSocket transient requests
* integrate the component into RequestTabs for user interaction
* update collection and request handling to differentiate between transient and non-transient requests
* enhance Redux actions to support transient request creation and management

* feat: enhance transient request handling and add temp directory watcher

* refactor Redux actions for HTTP, gRPC, and WebSocket requests to utilize a unified task queue for transient requests
* implement a new helper function to retrieve collection paths from temporary directory metadata
* add functionality to watch transient directories for file changes, excluding metadata.json
* integrate transient directory watcher into the collection mounting process

* feat: enhance transient request management with temp directory integration

* update generateTransientRequestName function to accept tempDirectory parameter for improved request naming
* modify CreateTransientRequest component to utilize tempDirectory for transient request creation
* adjust middleware to check for transient status based on tempDirectory
* implement transient file and directory identification in collections slice for better state management

* feat: add SaveTransientRequest component for managing transient requests

* implement SaveTransientRequest component to facilitate saving transient requests to selected folders
* create StyledWrapper for component styling
* introduce useCollectionFolderTree hook for managing folder navigation and state
* update Redux actions to handle saving requests from transient state

* feat: implement SaveTransientRequestContainer and enhance modal management

* add SaveTransientRequestContainer to manage multiple transient request modals
* refactor SaveTransientRequest component to utilize Redux for modal state management
* implement open and close actions for transient request modals in Redux slice
* update Bruno page to include SaveTransientRequestContainer for improved UI integration

* feat: enhance SaveTransientRequest component with new folder creation functionality

* add input for creating new folders within the SaveTransientRequest component
* implement validation for new folder names and filesystem names
* integrate folder creation logic with Redux actions for better state management
* update styling for new folder input elements in StyledWrapper
* improve modal behavior to reset state when opened

* feat: update CreateTransientRequest to utilize collection presets for request URLs

* Refactored CreateTransientRequest component to retrieve request URLs from collection presets.
* Enhanced request handling by dynamically setting request URLs based on the selected collection's configuration.

* refactor: clean up unused imports and adjust request handling in collections actions

* Removed unused imports from actions.js to streamline the code.
* Updated the saveRequest function to reject the modal instead of resolving it when handling transient requests.
* Cleaned up comments in index.js for better clarity.

* refactor: streamline transient request handling and improve save functionality

* Removed success toast notifications from CreateTransientRequest component to simplify user feedback.
* Enhanced SaveTransientRequest component to handle transient requests more effectively, including improved filename resolution and validation.
* Added IPC handler for saving transient requests, ensuring proper file management and error handling.
* Updated Redux actions to check for duplicate transient request names within the temporary directory.

* feat: enhance request handling in ConfirmCollectionCloseDrafts component

* Added logic to differentiate between transient and non-transient drafts, ensuring transient requests are saved individually before closing the collection.
* Improved user feedback by displaying unsaved changes for both regular and transient requests.
* Updated save and discard functionality to handle all drafts appropriately, enhancing overall user experience.

* fix:fixed useCallback dependency array

* fix:added request name checks before save

* fix: added isTransient to files

* fix: added watcher cleanup for temp directory

* refactor: enhance transient request handling and optimize component logic

* Updated CreateTransientRequest to utilize useMemo for improved performance and prevent unnecessary re-renders.
* Refactored generateTransientRequestName to focus solely on transient requests, removing tempDirectory dependency.
* Streamlined SaveTransientRequest by consolidating form reset logic and removing unused state variables.
* Improved ConfirmCollectionCloseDrafts to differentiate between transient and non-transient drafts more effectively.
* Cleaned up imports and optimized Redux actions for better maintainability.

* feat: implement transient request file deletion on tab close

* Added middleware to handle the deletion of transient request files when tabs are closed.
* Enhanced collection-watcher to unlink temporary files, ensuring metadata.json is skipped and only request files are processed.
* Improved error handling for file deletion operations.

* feat: enhance autosave middleware to skip transient requests

* Updated autosave middleware to check for transient requests and skip auto-save operations accordingly.

* fix: update ConfirmCollectionCloseDrafts to display all transient drafts

* Modified the ConfirmCollectionCloseDrafts component to show all transient drafts without limiting the display to a maximum number.
* Removed the conditional message for additional drafts not shown, enhancing the user experience by providing complete visibility of transient requests.

* feat: enhance SaveTransientRequest component for better modal management

* Refactored SaveTransientRequest and its container to improve modal handling for unsaved transient requests.
* Introduced state management for opening specific modals and added functionality to discard all unsaved requests.
* Updated Redux actions to manage transient request modals more effectively, ensuring no duplicates are added.
* Enhanced user interface to display a list of unsaved requests with options to save or discard them.

* feat: improve modal management in SaveTransientRequestContainer

* Added useEffect to reset openItemUid when the corresponding modal is no longer present.
* Implemented functionality to close all tabs associated with transient requests and show a success message upon discarding them.
* Removed unnecessary modal close handler and streamlined modal opening logic for better clarity and performance.

* refactor: streamline code formatting and improve readability in collection actions

* Consolidated multiple lines of code into single lines for better readability in ConfirmCollectionCloseDrafts and actions.js.
* Enhanced consistency in the formatting of function parameters and return statements across the collections slice.
* Removed unnecessary line breaks and improved the structure of the code for easier maintenance.

* refactor: improve code readability and structure in middleware and actions

* Consolidated multiple lines of code into single lines for better readability in middleware.js and actions.js.
* Enhanced consistency in formatting function parameters and return statements across the collections slice.
* Removed unnecessary line breaks and improved the structure of the code for easier maintenance.
* Streamlined dispatch calls for better clarity and performance.

* refactor: enhance code readability and consistency in middleware and actions

* Improved formatting and structure in middleware.js for dispatch calls.
* Streamlined comments and indentation in actions.js for better clarity.
* Consolidated multiple lines into single lines where appropriate to enhance readability.

* refactor: enhance transient request handling and modal interactions

* Improved the modal handling logic for removing collections to differentiate between regular and drafts confirmation modals.
* Added new tests for creating and saving transient requests (HTTP, GraphQL, gRPC, WebSocket) ensuring they do not appear in the sidebar until saved.
* Introduced utility functions for creating transient requests and filling request URLs, improving code reusability and clarity.

* refactor: simplify transient request modal rendering and improve collection watcher logic

* Introduced a new TransientRequestModalsRenderer component to streamline modal rendering based on the number of transient requests.
* Refactored the collection watcher logic to enhance readability by removing unnecessary setTimeout and consolidating file handling functions.
* Improved error handling and logging for the temp directory watcher.

* fix: correct spelling of 'WebSocket' in transient request components and tests

* Updated the spelling of 'Websocket' to 'WebSocket' in CreateTransientRequest component, transient requests test, and action type definitions for consistency and accuracy.
2026-01-29 18:38:42 +05:30
sanish chirayath
4b724ebd85 fix: async fns calls window.send after the window is destroyed (#6747)
* fix: app crash error

* fix: prevent app crash by ensuring window and webContents are not destroyed before sending messages
2026-01-29 15:43:04 +05:30
lohit
b3a66e9c3c fix: system proxy resolver updates (#6273) 2026-01-29 14:25:13 +05:30
gopu-bruno
4f327b7b77 fix: prevent crash when reordering query params with empty name (#6938)
* fix: prevent crash when reordering query params with empty name

* fix: prevent crash when reordering rows with empty key field
2026-01-29 13:56:40 +05:30
Sid
579cda1d1a chore: remove prettier configurations (#6967)
* chore: remove prettier

* chore: remove prettier commands

* chore: fix newline at end of package-lock.json
2026-01-29 13:31:00 +05:30
so-iwamoto
61199fb966 Add Buffer.isBuffer() check to skip interpolation for Buffer data (e.g., gzip-compressed bodies). (#6922)
Co-authored-by: Sora Iwamoto <rockbook2025@gmail.com>
2026-01-29 13:01:07 +05:30
Pierre GUYOT
79daf7700f docs(converters): Fix example code to await (#6960)
Updated example code to use await with postmanToBruno function.

Also change markdown code from `bash copy` to `javascript`
2026-01-29 12:19:09 +05:30
Abhishek S Lal
6e34fbd0ce fix: ensure terminal gains focus upon opening in the console tab (#6951) 2026-01-28 20:38:20 +05:30
Sid
1fcf9ecc32 Update React coding standards in CODING_STANDARDS.md (#6962)
Added new coding standards for React components and hooks usage.
2026-01-28 20:00:58 +05:30
shubh-bruno
fec407e2eb fix: path-param variable edit popup (#6955)
Co-authored-by: shubh-bruno <shubh-bruno@shubh-bruno.local>
2026-01-28 19:37:15 +05:30
sanish chirayath
5044241d17 feat: enhance translation for request and response URLs (#6956) 2026-01-28 18:59:14 +05:30
sanish chirayath
584344ac47 Fix: duplicate toast when saving an example (#6952)
* chore: update saveRequest calls from example to include silent parameter

- Modified multiple components to pass an additional `true` parameter to the `saveRequest` function, ensuring requests are saved silently when it comes to examples.
- Added AGENTS.md to .gitignore to exclude it from version control.

* feat: add success notifications for cloning, renaming and deleting examples

* refactor: update saveRequest calls to handle success notifications for renaming and deleting examples

* refactor: remove unused toast import from DeleteResponseExampleModal
2026-01-28 17:59:03 +05:30
Pooja
d975d0b642 fix: openapi spec with example values (#6476)
* fix: openapi spec with example values

* fix

* fix
2026-01-28 16:03:00 +05:30
shubh-bruno
af6908e9c0 fix: disable editing runtime variable if same as collection (#6835)
Co-authored-by: shubh-bruno <shubh-bruno@shubh-brunos-MacBook-Air.local>
2026-01-27 22:08:41 +05:30
Pooja
21673f46de feat: add header validation (#6859)
* feat: add header validation

* fix: test stability

* fix: scope the locator

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2026-01-27 22:06:17 +05:30
Sid
51276beaf1 chore: reduce flakiness when running parallel tests (#6848) 2026-01-27 19:56:48 +05:30
naman-bruno
7661af34c8 consistent string handling across parsers (#6866)
* consistent string handling across parsers

* fix
2026-01-27 14:10:40 +05:30
Pragadesh-45
01b87ee71c refactor: improve element interactions in preferences and fix playwright tests (#6894) 2026-01-22 20:16:33 +05:30
Abhishek S Lal
9e1c58ab6f Remove isLikelyText detection from buffer content type utility (#6870) 2026-01-22 12:40:57 +05:30
Kanak
0fb605a684 fix: update linting commands to use npx for better compatibility (#6840)
* fix: update linting commands to use npx for better compatibility

* chore: update cross-env version and adjust lint scripts

* Fix lint command syntax in package.json

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2026-01-21 21:55:34 +05:30
sanish chirayath
5fd3948028 Feature: send request translation (#6792)
* feat: implement translation utilities for converting Bruno scripts to Postman format

- Added `bru-to-pm-translator` for translating Bruno API calls to Postman equivalents.
- Introduced `pm-to-bru-translator` for reverse translations from Postman to Bruno.
- Created utility functions in `ast-utils` for efficient AST manipulations.
- Enhanced `bruno-to-postman.js` to utilize the new translation functions for script handling.
- Updated tests to cover various translation scenarios, ensuring accuracy and reliability.

* empry commint

* refactor: migrate utility functions to ES module syntax

- Converted utility functions in `ast-utils.js` to named exports for better modularity.
- Updated import statements in `bru-to-pm-translator.js` and `pm-to-bru-translator.js` to use ES module syntax.
- Refactored test files to align with the new import structure, enhancing consistency across the codebase.

* fix: translations

* fix: add info regarding cookie apis

* simplify translations removing legacy inverse translation

* fix: add translation for getFolderVAr

* refactor: simplify transformation functions by removing change tracker

* fix: renamed files and folders

* fix: import statements

* rm : file

* simplify getSize translation

* rebase

* fix: rebase

* fix: update transformCallback to support async functions

* feat: enhance object transformation to support spread operators in request data

* refactor: transform body function

* feat: added request transformation testcases, refactor
2026-01-21 20:26:13 +05:30
lohit
3e92c44a5a Merge pull request #6748 from lohit-bruno/bruno_app_instances_handling
feat: `bruno app instances` handling updates
2026-01-21 19:53:17 +05:30
Pragadesh-45
67c1d39e60 feat: preferences as tab (#6786)
* feat: preferences as tab

refactor: remove preferences tab from permanent tabs and update tab label handling

fix: comment

Co-authored-by: Sid <siddharth@usebruno.com>

* refactor: replace Checkbox component with native input elements in Preferences and ProxySettings

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2026-01-21 19:22:28 +05:30
Pragadesh-45
2288121f70 feat: status indicator for pre and post request scripts (#6865) 2026-01-21 19:01:06 +05:30
Pooja
a22eb43a27 fix(websocket): add API Key query params support and OAuth2 inheritan… (#6271)
* fix(websocket): add API Key query params support and OAuth2 inheritance warning

* add: playwright test
2026-01-21 18:56:46 +05:30
Sid
27b7fa81f2 feat: js api supports get path params (#5235) (#6762) 2026-01-21 18:41:47 +05:30
Bijin A B
1f571267b0 Merge pull request #6814 from sanjaikumar-bruno/fix/basic-auth-codegen
fix: Code Generation for Basic Auth
2026-01-21 18:38:46 +05:30
Sanjai Kumar
0b79ce9095 fix: correct action type string for global environments in autosave middleware (#6872) 2026-01-21 18:33:09 +05:30
Pooja
75e17610f0 fix: openapi query param import (#6241) 2026-01-21 17:53:14 +05:30
sanjai
acf576872c enhance: snippet generator to support header interpolation
refactor: improve snippet generation and update test cases

updated to minimise changes

fix: remove exclusive test flag

refactor: enhance interpolation utilities

refactor: expand interpolation utilities for auth, headers, body, and params

refactor: simplify request handling in snippet generation by removing lodash dependency and clarifying auth header processing

fix: tests

refactor: integrate interpolateObject utility for enhanced interpolation across auth, headers, body, and params

refactor: streamline body interpolation by removing lodash dependency and returning updated body structure

refactor: enhance body interpolation logic and streamline auth header processing in snippet generation

refactor: simplify getAuthHeaders function by removing unnecessary parameters for improved clarity

refactor: replace interpolateObject with interpolate for body
2026-01-21 15:55:21 +05:30
Cmarvin1
c94785f521 Adding interpolation utilities
Refactor interpolation

Refactor interpolation

updating tests

updating tests

minor refinements to interpolation logic

update snippet generator to handle basic auth credentials

move interpolation upstream
2026-01-21 15:55:11 +05:30
sreelakshmi-bruno
154c45d87d skip loading CA certificates when SSL verification is disabled (#6829) 2026-01-21 12:38:45 +05:30
Yash
0bf169562b feat: enhance OAuth2 support in snippet generation (#6592)
* feat: enhance OAuth2 support in snippet generation

* Updated getAuthHeaders function to handle OAuth2 authentication, including token retrieval and placement.
* Added tests for OAuth2 scenarios, ensuring correct Authorization header generation and handling of edge cases.
* Improved error handling for access token retrieval from stored credentials.

* refactor: standardize comparison operators in getAuthHeaders function

* Updated comparison operators in the getAuthHeaders function to use strict equality (===) for improved consistency and reliability in credential checks.

* fix: correct block structure in OAuth2 case of getAuthHeaders function

* Added missing block structure for the 'oauth2' case in the getAuthHeaders function to ensure proper execution flow and maintain code clarity.

* feat: enhance OAuth2 credential retrieval in getAuthHeaders function

* Updated getAuthHeaders function to support retrieval of stored OAuth2 credentials based on collection and item context.
* Improved access token handling by checking for existing credentials before falling back to default values.
* Enhanced test coverage for OAuth2 scenarios to ensure accurate token management and error handling.

* fix: preserve tokenHeaderPrefix value in OAuth2 configuration

* Updated snippet-generator.spec.js to ensure that the tokenHeaderPrefix from OAuth2 configuration is preserved, allowing for empty string scenarios.
* Default to 'Bearer' only if the tokenHeaderPrefix is undefined, enhancing flexibility in token management.

* fix: ensure consistent formatting of authorization header in OAuth2 handling

* Updated getAuthHeaders function to always trim the final result of the authorization header for consistent formatting.
* Adjusted snippet-generator.spec.js to reflect the same trimming logic for the access token, enhancing test reliability.

* fix: clarify token placement handling in getAuthHeaders function

* Updated comments in the getAuthHeaders function to specify that when tokenPlacement is 'url', no auth headers are added, and that token placement in the URL/query params must be managed separately.

* fix: ensure safe handling of OAuth2 credentials in getAuthHeaders function

* Updated getAuthHeaders function to default to an empty array when accessing oauth2Credentials, preventing potential errors when no credentials are available.
2026-01-21 12:23:05 +05:30
gopu-bruno
967b073ded fix: prevent response truncation in recursive collection runner (#6862) 2026-01-21 11:32:33 +05:30
sanish chirayath
725dfeacac feat: add user-agent support in gRPC client channel options (#6808)
* feat: add user-agent support in gRPC client channel options

- Extracted user-agent from request headers and set it as grpc.primary_user_agent channel option.
- Updated client instantiation to merge user-agent with existing channel options for enhanced request handling.

* test: add unit tests for GrpcClient user-agent handling

* test: enhance GrpcClient user-agent tests with edge case handling

* test: enhance GrpcClient channelOptions handling with override capability
2026-01-20 23:59:25 +05:30
lohit
923d26ce56 fix: get certs and proxy config based on oauth2 token and refresh urls instead of resource url (#6164) 2026-01-20 21:43:54 +05:30
lohit
7e258003d5 feat: add certs and proxy config for bruno-cli oauth2 requests (#6423) 2026-01-20 21:42:48 +05:30
Abhinandan M.S
7689288763 fix:prevent JS hint leak on Ctrl+Space and show allowed root hints (#6776) 2026-01-20 13:55:49 +05:30
fake
81faa57808 fix: add timeout for prevent ui lag (#6771) 2026-01-20 13:28:06 +05:30
naman-bruno
bac9616de4 feat: enhance SaveRequestsModal to handle environment drafts (#6857) 2026-01-20 12:40:06 +05:30
naman-bruno
9ab1ed3d90 fix: update clone collection location logic based on active workspace (#6841) 2026-01-20 12:38:31 +05:30
Sid
408c9d4a4e chore: update project dependencies (#6858) 2026-01-20 12:37:09 +05:30
Sid
ebafdd813c chore: update qs package version to 6.14.1 (#6849)
Co-authored-by: Siddharth Gelera <ahoy@barelyhuman.dev>
2026-01-19 21:40:39 +05:30
lohit
6642f4d0b0 fix: cli proxy config updates (#6846)
* fix: cli `proxy config` updates

* fix: review comment fixes
2026-01-19 20:58:20 +05:30
naman-bruno
4f75474c87 remove allowScriptFilesystemAccess flag (#6834) 2026-01-19 19:33:00 +05:30
Bijin A B
e5b7aa5ab4 fix: variables set via setVar should be interpolated only during runtime (#6823) 2026-01-19 17:28:36 +05:30
Sid
875df38501 Merge pull request #6662 from usebruno/feature/bru-safemode-5760
feat: Implement `isSafeMode()` API (#5760)
2026-01-19 17:26:45 +05:30
Pooja
a724f010ff fix bru safe mode and add test (#6667)
* fix bru safe mode and add tests

* rm: settimeout

fix: isSafe mode test (#6844)
2026-01-19 17:26:12 +05:30
Dominik D. Geyer
f9423d1238 feat: Implement isSafeMode() API (#5760)
Add `isSafeMode()` to Bru API that returns `true` in
case the runtime is a sandbox.

This allows for scripts to test for and handle whether
running in sandbox or not:

```javascript
if (bru.isSafeMode()) {
  throw new Error('This script requires Developer mode')
}
```

Co-authored-by: Anoop M D <anoop@usebruno.com>
2026-01-19 17:26:12 +05:30
shubh-bruno
51e36519f7 fix: improve {{var}} detection using cursor-based brace matching (#6691) 2026-01-19 17:17:09 +05:30
gopu-bruno
bd0894ede0 fix: resolve tab flickering when switching between requests (#6825) 2026-01-16 12:08:14 +05:30
Pragadesh-45
b1e6a707bf feat: add support for interpolation on mockDataFunctions (#6393)
feat: implement `prepareMockObj` function for enhanced mock data processing in interpolation
2026-01-14 21:58:03 +05:30
Sanjai Kumar
c51381888a fix: basic Auth inheritance in code generation (#6805)
* fix: include auth in request data for GenerateCodeItem

* fix: conditionally include auth in request data for GenerateCodeItem

* fix: simplify auth inclusion in requestData

* fix: streamline auth assignment in requestData for GenerateCodeItem

* fix: clarify comments on auth resolution
2026-01-14 21:12:04 +05:30
sreelakshmi-bruno
8b1b18cc39 fix: resolve Load Request button error when loading large collection … (#6809)
* fix: resolve Load Request button error when loading large collection requests

* scope down to .bru requests
2026-01-14 19:25:39 +05:30
Pooja
707ed63be6 fix: timestamp tooltip message (#6688) 2026-01-14 17:13:54 +05:30
Chirag Chandrashekhar
bc0bb64400 fix: prevent URL marking within variable patterns in CodeMirror (#6680) 2026-01-14 14:00:28 +05:30
shubh-bruno
4c110900c1 fix: rename requests double notifications (#6677) 2026-01-14 13:28:35 +05:30
shubh-bruno
65ed6d3cfb fix: response format auto-switch on content type change (#6773) 2026-01-14 13:28:04 +05:30
Sid
7b28b05bc1 fix: add compute key for virtual table (#6807) 2026-01-14 13:09:18 +05:30
shubh-bruno
b0f27d01b9 fix: env vars loading and switching using react-virtuoso (#6790)
* fix: environment variables fetching and switching

* chore: formatting

* fix: support active, inactive, secret vars popup

* fix: variable highlight styles

* chore: codemirror styles

* fix: show variable highlighting when editor is inactive

* fix: tab press for switching columns

* fix: environment variables loading with react-virtuoso

* fix: refactor EnvironmentVariables component for improved table rendering

* fix: update react-virtuoso to version 4.18.1

---------

Co-authored-by: shubh-bruno <shubh-bruno@shubh-brunos-MacBook-Air.local>
Co-authored-by: Sid <siddharth@usebruno.com>
2026-01-14 12:24:07 +05:30
Pooja
3351bf990a fix: websocket message scroll (#6503)
* fix: websocket message scroll

* fix

* fix: icon color

* fix: sse message list

* fix

* rm: sort test

* rm: WSResponseSortOrder

* fix: auto scroll
2026-01-13 22:09:08 +05:30
lohit
07fff423bb feat: add node-vault util functions (#6796)
* feat: add `node-vault` util functions

* fix: review comment fixes
2026-01-13 22:06:40 +05:30
sanish chirayath
36d10ab480 feat: implement translation utilities for converting Bruno scripts pm format (#6761)
* feat: implement translation utilities for converting Bruno scripts to Postman format

- Added `bru-to-pm-translator` for translating Bruno API calls to Postman equivalents.
- Introduced `pm-to-bru-translator` for reverse translations from Postman to Bruno.
- Created utility functions in `ast-utils` for efficient AST manipulations.
- Enhanced `bruno-to-postman.js` to utilize the new translation functions for script handling.
- Updated tests to cover various translation scenarios, ensuring accuracy and reliability.

* empry commint

* refactor: migrate utility functions to ES module syntax

- Converted utility functions in `ast-utils.js` to named exports for better modularity.
- Updated import statements in `bru-to-pm-translator.js` and `pm-to-bru-translator.js` to use ES module syntax.
- Refactored test files to align with the new import structure, enhancing consistency across the codebase.

* fix: translations

* fix: add info regarding cookie apis

* simplify translations removing legacy inverse translation

* fix: add translation for getFolderVAr

* refactor: simplify transformation functions by removing change tracker

* fix: renamed files and folders

* fix: import statements

* rm : file

* simplify getSize translation
2026-01-13 20:03:14 +05:30
Abhishek S Lal
c918c679d7 fix: handle optional clientSecret in OAuth2 authorization header (#6186)
* fix: handle optional clientSecret in OAuth2 authorization header

* style: standardize string quotes in OAuth2 token functions

* test: add comprehensive tests for OAuth2 client credentials and password grant flows
2026-01-13 19:30:11 +05:30
Abhishek S Lal
7e3386b1b8 feat: allow collection environment and environment file to be used together in run command (#6784) 2026-01-13 19:24:26 +05:30
Sanjai Kumar
f4162e1ce6 feat: show skipped requests with parsing errors in report (#6780)
* feat: add support for skipped files in run command and update HTML report template

* refactor: enhance skipped file handling in run command

* fix: improve error display in HTML report for skipped requests

* test: add unit test for HTML report generation of skipped requests with parsing errors

* test: update HTML report generation tests to check for skipped request summaries

* refactor: extract skipped result creation logic into a separate utility function

* refactor: enhance skipped result processing in run command to include additional metadata

* refactor: rename and enhance createSkippedResults function for improved skipped file processing

* refactor: remove unused stripExtension import from run command

* refactor: rename createSkippedResults to createSkippedFileResults for clarity and consistency
2026-01-13 18:51:27 +05:30
gopu-bruno
e6a48a73bf fix: move yup from peerDependencies to dependencies (#6794)
* fix: move yup from peerDependencies to dependencies

* chore: update package-lock.json
2026-01-13 18:49:11 +05:30
Sanjai Kumar
fceb99edc2 fix: autosave for environment tabs and folder-level auth (#6510)
* feat: enhance autosave middleware to support environment draft and folder auth drafts

* feat: extend autosave middleware to handle global and collection environment drafts

* feat: update authentication components to use unified updateAuth function

* refactor: rename updateAuth to updateFolderAuth for consistency in authentication components
2026-01-13 16:16:46 +05:30
Pooja
ebc105d42e add: autosave missing actions in middleware (#6781) 2026-01-13 12:38:07 +05:30
Abhishek S Lal
32d56f6942 fix: update modal size in CreateEnvironment component from small to medium (#6791) 2026-01-13 12:35:44 +05:30
Abhishek S Lal
e4a1fca3b1 feat: Improve response content type detection and SVG handling (#6741)
* feat: add SVG support for HTML preview in response format handling

* feat: enhance content type detection by adding support for AVIF and SVG formats

* fix: exclude SVG from byte format type detection in response preview

* feat: add helper function to detect SVG content in response handling

* fix: ensure SVG content type detection is case insensitive and remove EPS detection

* fix: correct byte offset for AVIF content type detection in response handling
2026-01-13 12:33:55 +05:30
Abhishek S Lal
59ff9bdafb Bugfix/workspace name case mismatch (#6560)
* fix: preserve workspace name casing in title bar (#6522)

* fix: improve workspace display name handling in title bar

---------

Co-authored-by: Uzairkazi695 <kaziuzair695@gmail.com>
2026-01-12 23:15:39 +05:30
naman-bruno
071ee9ab2e feat: workspace .env file support (#6777) 2026-01-12 13:40:38 +05:30
naman-bruno
176646f983 feat: add default .gitignore file creation in workspace and collection (#6778) 2026-01-12 13:26:45 +05:30
Anas Najam
d76a574c51 correct GitHub version badge URL in readme files (#6772) 2026-01-12 12:39:02 +05:30
Pragadesh-45
734ee16fe1 feat: apply modified dataBuffer to the response (#6023)
* feat: apply modified dataBuffer to the response

* fix: ensure dataBuffer regeneration only occurs when res.setBody() is called

* refactor: update dataBuffer handling in BrunoResponse
2026-01-12 11:39:26 +05:30
Pragadesh-45
33594bdcec feat: add zoom controls to key mappings (#6765) 2026-01-09 19:50:36 +05:30
Ryan
2acfe60a5f feat/ Theme-dependent screenshots in README (#6738)
* feat: add theme-aware landing image for dark/light mode

* feat: update README to use theme-aware landing images

* fix: correct filename spacing and theme logic for landing images
2026-01-09 18:58:37 +05:30
Sid
aecaab84dd feat: add script to list changed packages (#6678) 2026-01-09 16:30:52 +05:30
Abhishek S Lal
45264bfcc5 refactor: enhance WSRequestPane and WSResponsePane with ResponsiveTabs component (#6650)
* refactor: enhance WSRequestPane and WSResponsePane with ResponsiveTabs component

- Replaced custom tab implementation with ResponsiveTabs for better consistency and usability.
- Utilized useMemo and useCallback for performance optimizations in tab management.
- Cleaned up unused styles and improved error handling in both components.
- Updated StyledWrapper to remove legacy tab styles, streamlining the component structure.

* refactor: streamline authentication components and enhance WSRequestPane layout

- Removed unnecessary margin from StyledWrapper in ApiKeyAuth, BasicAuth, and BearerAuth components for a cleaner layout.
- Introduced a new right content area in WSRequestPane for better organization of authentication modes.
- Added a 'No Auth' view in WSAuth for improved user feedback when no authentication is selected.
- Cleaned up unused imports and optimized component structure for maintainability.
2026-01-09 14:06:15 +05:30
sanish chirayath
b01b8d7bc4 fix: grpc import paths (#6726)
* fix: grpc import paths

* refactor: extract protobuf include directory logic into a separate function

* rm: comment

* fix: improve filtering of enabled import paths in protobuf configuration

* refactor: streamline import path handling in protobuf configuration
2026-01-08 21:16:47 +05:30
Abhishek S Lal
58a38ac5a1 refactor: enhance tab management in ResponseExampleResponsePane component (#6655)
- Removed local state for activeTab and integrated Redux for tab state management.
- Added logic to retrieve and update the active tab using Redux store.
- Updated tab click handler to dispatch actions for tab changes.
2026-01-08 20:43:41 +05:30
Abhishek S Lal
7328988e59 refactor: simplify HtmlPreview component by extracting render logic into a separate function (#6740)
* refactor: simplify HtmlPreview component by extracting render logic into a separate function

* refactor: wrap renderHtmlPreview in a fragment for improved JSX structure

* fix: update preview visibility check in response format tests
2026-01-08 20:04:38 +05:30
Sanjai Kumar
39a6fc837d fix: Handle deleted environment variables in UI (#6703)
* fix: enhance environment variable management in collections slice

* test: refactor deleteEnvVar test
2026-01-08 20:00:43 +05:30
Sanjai Kumar
5b1b1b5541 fix: ephemeral environment variables being saved to filesystem (#6723)
* refactor: enhance environment variable persistence logic

* refactor: simplify environment variable persistence checks
2026-01-08 19:59:01 +05:30
Abhishek S Lal
578fa72dc8 refactor: enhance GrpcRequestPane and GrpcResponsePane with ResponsiveTabs component (#6649)
* refactor: enhance GrpcRequestPane and GrpcResponsePane with ResponsiveTabs component

- Replaced custom tab implementation with ResponsiveTabs for better structure and usability.
- Utilized useMemo and useCallback for performance optimizations in GrpcRequestPane.
- Removed unused imports and simplified tab management logic.
- Updated StyledWrapper to remove legacy tab styles, improving maintainability.

* fix: handle optional chaining for auth mode in GrpcRequestPane

* feat: enhance GrpcRequestPane and GrpcResponsePane with tab initialization and response count indicators

* refactor: simplify GrpcResponsePane tab management and enhance ResponsiveTabs key handling

- Removed unnecessary useMemo for tab initialization in GrpcResponsePane.
- Updated tab comparison logic in ResponsiveTabs to use key arrays for improved performance.
- Adjusted test locator for response tab count to use role-based selection for better accessibility.

* feat: add support for 'none' auth mode in GrpcAuth and integrate GrpcAuthMode in GrpcRequestPane

- Updated StyledWrapper in ApiKeyAuth, BasicAuth, BearerAuth, OAuth2, WsseAuth, and GrpcAuth components to remove unnecessary margin-top, ensuring a uniform appearance across authentication interfaces.
- Adjusted margin in GrantTypeSelector and WSAuth components for better layout consistency.

* refactor: update import statement and enhance error handling in GrpcRequestPane

- Changed the import of 'find' from lodash to a direct import for better clarity.
- Improved error handling by returning null during initialization when requestPaneTab is not set, ensuring smoother user experience.

* refactor: integrate StyledWrapper in SearchInput for improved styling

* refactor: update StyledWrapper color and adjust margin in GrpcTimelineItem for improved layout consistency
2026-01-08 15:25:39 +05:30
Sanjai Kumar
4708e8e589 fix: enhance collection item drop logic to prevent invalid moves (#6727) 2026-01-08 14:02:16 +05:30
Abhishek S Lal
2a9386ef6b fix: update ResponseExampleUrlBar styles for better overflow handling (#6535) 2026-01-08 02:34:23 +05:30
naman-bruno
9483dbf4af fix: yml format registration on collection import (#6735) 2026-01-08 00:49:48 +05:30
Abhishek S Lal
0b436e2c9f refactor: remove HTML validation functions and simplify HtmlPreview component logic (#6730)
* refactor: remove HTML validation functions and simplify HtmlPreview component logic

* chore: fix playwright - removed body value check since response is rendered in webview

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-07 22:46:31 +05:30
Anoop M D
9005e17eb5 Merge pull request #6707 from naman-bruno/improve/default-migration
improve: migration & default workspace handling
2026-01-07 19:14:01 +05:30
gopu-bruno
efe94d9c90 fix: Large Response Warning download button functionality (#6695)
* fix: Large Response Warning download button functionality
2026-01-07 15:23:10 +05:30
ganesh
848825f16a Change home image in readme with v3 UI (#6699)
* home image

* change usebruno to Bruno

* snapshot with default theme
2026-01-07 15:22:21 +05:30
naman-bruno
4d60425a05 fix: workspace already opened (#6721) 2026-01-07 14:35:03 +05:30
Abhishek S Lal
c03fe301f8 fix: avoid error toast while pasting non-cURL value in GQL url field (#6718) 2026-01-07 13:27:38 +05:30
Bijin A B
c8371410c2 chore: minor url bar alignment fixes and refactor (#6714) 2026-01-07 08:20:02 +05:30
Chirag Chandrashekhar
dcbf38bf61 fix: Query URL overflow pushes the action buttons outside view in gRPC and HTTP (#6706)
- Updated the QueryUrl component to handle text overflow. Now, overflow triggers scroll and does not move the action buttons out of view.
- Updated the GrpcQueryUrl component to handle text overflow. Now, overflow triggers scroll and does not move the action buttons out of view.
2026-01-07 07:40:58 +05:30
naman-bruno
a57ecde1d0 improve: migration & default workspace handling 2026-01-06 21:57:25 +05:30
Abhishek S Lal
791843174e refactor: improve tab state management in ResponsiveTabs component (#6687) 2026-01-06 17:13:56 +05:30
Pragadesh-45
1174f22d88 fix: improve Content-Type handling when request body is none (#6486) (#6540) 2026-01-06 17:02:53 +05:30
naman-bruno
8300abe086 fix: auth in cli (#6675)
* fix: auth in cli

* fixes
2026-01-05 22:06:26 +05:30
Abhishek S Lal
a3809ce4b9 style: remove unnecessary padding from pre elements in StyledWrapper component (#6674) 2026-01-05 20:39:58 +05:30
gopu-bruno
adb46110dd Fix/ws environment input alignment (#6672)
* style: enhance EnvironmentList component with improved flex properties

* refactor: remove report issue link for YAML format in CreateCollection component
2026-01-05 20:28:56 +05:30
naman-bruno
7cc4c0993e fix: atomic write issue (#6664) 2026-01-05 17:29:03 +05:30
Abhishek S Lal
1030d02ac7 fix: update hover background color in dark theme (#6666) 2026-01-05 17:28:49 +05:30
Anoop M D
d616be7271 Merge pull request #6661 from naman-bruno/cli/opencollection
add: oc support for cli
2026-01-05 16:09:51 +05:30
naman-bruno
afd49d146f add: oc support for cli 2026-01-05 15:57:49 +05:30
Abhishek S Lal
97e43c4489 feat: add native select styling to global styles (#6660) 2026-01-05 15:52:34 +05:30
gopu-bruno
f9af22d586 fix: apply infoTip styling to CodeMirror tooltip (#6658)
* style: apply infoTip styling to CodeMirror tooltip

* fix: add CodeMirror lint tooltip warning and  error text colors

* fix: update font size of CodeMirror lint tooltip
2026-01-05 14:16:52 +05:30
sreelakshmi-bruno
8590bacd79 add license and readme to bruno query package (#6654) 2026-01-05 13:22:54 +05:30
Bijin A B
a7d1a349e3 fix: lighten dark pastel theme modal background color (#6653) 2026-01-04 21:46:04 +05:30
Anoop M D
d03d8f01a1 feat: update @opencollection/types to version 0.7.0 and add demo image to GenerateDocumentation component (#6651) 2026-01-04 21:28:19 +05:30
lohit
97c700beba fix: update logic for checking formdata instances (#6643)
* fix: update logic for checking formdata instance

* fix: isFormData logic update

* fix: review comment fix, add isFormData to @usebruno/common package

* fix: review comment fix
2026-01-04 21:27:07 +05:30
Sid
b6a27bc66c fix: reverse sorting order for websocket messages (#6652) 2026-01-04 16:54:27 +05:30
Bijin A B
76a2889206 fix(ux): fix sidebar invisible for environments tab, grpc and ws (#6648) 2026-01-04 12:40:22 +05:30
Bijin A B
d506c37516 chore: adjust indent borders, table stripes, modal bg & tooltip bg (#6646) 2026-01-03 23:40:39 +05:30
naman-bruno
0c4ad0ed60 fix: escape closing tags in genrated docs (#6645) 2026-01-03 20:28:36 +05:30
SHUBH
30dbe34e2e fix: add a hard sequence in SSE and WS requests (#6569)
* fix: sse sequence in ipc layer

* fix: remove tick rate and flushing

* fix: added sequence logic for websockets

* fix: added sequence logic for websockets per request based

* fix: correct the order for how the messages are added.

`WSMessagesList` already handles a lot of the ordering for us, don't modify the order the messages are added since redirect and connection are internal states, it changes the execution trail

* chore: reduce whitespace diffs

* fix: a possible null case exception

Though we always create an empty data buffer at source so shouldn't happen unless that is modified

* fix: implement sequence logic for WebSocket messages

* fix: remove unused sequenceState property from WsClient

* fix: update message sorting logic to handle missing sequence numbers

* fix: remove unused lodash import

* fix: add clean method to sequencer for better sequence management

* fix: don't show dropdown when streaming

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2026-01-03 11:29:46 +05:30
Bijin A B
c83c055654 chore: theme updates (#6642) 2026-01-03 00:08:14 +05:30
Abhishek S Lal
33f47ca5e4 style: update color references to use theme.draftColor for unsaved changes across multiple components (#6641) 2026-01-02 23:32:55 +05:30
Anoop M D
475707848e style: enhance syntax highlighting in GQL Docs and Doc Gen (#6640)
* style: enhance syntax highlighting and theme integration in QueryEditor and GenerateDocs components

* fix: fixed generate code theming issues
2026-01-02 22:47:19 +05:30
Chirag Chandrashekhar
2d76c444f6 feat: integrate theme support in ImportCollection component (#6639)
* feat: integrate theme support in ImportCollection component for improved styling consistency

* chore: remove debug console log from ImportCollection component

* refactor: update theme import in ImportCollection component for improved consistency
2026-01-02 22:16:52 +05:30
Abhishek S Lal
33361ba659 refactor: update Tabs component structure and theme colors (#6638)
- Replaced the div wrapper in the Tabs component with StyledWrapper for improved styling.
- Simplified TabsList and TabsTrigger components by removing theme dependency and using classnames for styling.
- Updated inactive background colors in multiple theme files to enhance visual consistency across themes.
2026-01-02 22:01:35 +05:30
Sanjai Kumar
1e3a0d9af3 style: update variable validation colors in StyledWrapper components to use theme colors (#6633) 2026-01-02 21:26:18 +05:30
Chirag Chandrashekhar
2a5ec854cc style: update text color classes and button hover effects in RunnerResults (#6637)
* style: update text color classes and button hover effects in RunnerResults and StyledWrapper components

* Update StyledWrapper.jsx

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-02 21:21:10 +05:30
Abhishek S Lal
d758645144 feat: update "Show in Folder" label based on platform in ManageWorkspace, Collection, CollectionItem, and WorkspaceHome components (#6623)
* feat: update "Show in Folder" label based on platform in ManageWorkspace, Collection, CollectionItem, and WorkspaceHome components

* refactor: remove duplicate "Rename" item push in CollectionItem component
2026-01-02 20:57:51 +05:30
Abhishek S Lal
902d9ff968 refactor: update color references in OAuth2 components to use theme.primary.text for improved consistency (#6629)
* refactor: update color references in OAuth2 components to use theme.primary.text for improved consistency

* refactor: update modal size in ImportCollectionLocation component for improved consistency

* refactor: set isActiveTab prop in QueryResponse component and update active color in StyledWrapper for consistency
2026-01-02 20:57:20 +05:30
sanish chirayath
781def844d fix: tooltip styling (#6632) 2026-01-02 20:53:47 +05:30
sanish chirayath
15065eb2f1 fix: file picker component overflow (#6635) 2026-01-02 20:52:32 +05:30
sanish chirayath
481fa4ed12 fix: authmode in graphql (#6636) 2026-01-02 20:51:19 +05:30
Sanjai Kumar
a35b455041 style: remove font size class from button (#6630) 2026-01-02 18:50:06 +05:30
gopu-bruno
bfc8968e24 fix: folder settings var panel table alignment (#6631) 2026-01-02 18:48:26 +05:30
Sanjai Kumar
f90f256f5f fix: modal icon colors to match button colors (#6624)
* style: update warning icon color in RemoveCollection and ConfirmSwitchEnv components to use theme colors

* fix: font size in BulkEditor

* style: update error message styling to use theme colors in QueryResult component

* style: update warning icon color

* style: update warning color in ConfirmSwitchEnv

* chore: minor pr comment

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-02 18:18:36 +05:30
Pooja
4253080f6b fix: toggle switch color (#6627) 2026-01-02 18:01:50 +05:30
naman-bruno
7a83641436 update cdn links (#6628) 2026-01-02 17:17:14 +05:30
Bijin A B
7105e4e8ed fix(UX): fix sandbox selector options background color (#6626) 2026-01-02 16:49:12 +05:30
Abhishek S Lal
849465d62a Feat/v3 UI updates (#6618)
* style: enhance button layout and input styles across multiple components for improved UI consistency

* style: update RequestsNotLoaded component with new warning styles and enhance theme color definitions for status indicators

* refactor: update theme usage across components for consistency

- Changed color references from theme.brand to theme.primary.text in various StyledWrapper components.
- Added hover effects to enhance UI interactivity in CollectionSettings and FolderSettings.
- Removed unnecessary margin and padding adjustments in several components for cleaner layout.
- Improved accessibility by ensuring aria attributes are correctly set in MenuDropdown.
- Standardized styling for method indicators in RequestPane components.

These changes aim to create a more cohesive look and feel across the application while adhering to the updated theme guidelines.

* refactor: clean up method selector styling in NewRequest component

* chore: temp playwright test fixes

* refactor: update modal sizes across various components for consistency

- Changed modal size from "sm" to "md" in RenameWorkspace, CreateApiSpec, CloneCollection, DeleteCollectionItem, and RenameCollection components.
- Improved styling in HttpMethodSelector by adding padding for better layout.
- Updated theme color references in multiple theme files to use a new palette structure for consistency and maintainability.

* refactor: enhance styling and theme integration in TimelineItem components

- Updated HttpMethodSelector to clarify padding calculation in comments.
- Integrated theme colors for OAuth2 indicator and timestamp in TimelineItem for better visual consistency.
- Adjusted Method component to use uppercase styling for method display.
- Modified RelativeTime component to apply muted text color for improved readability.
- Updated INFO color in dark and light themes for better contrast and accessibility.

* refactor: remove duplicate import statements in theme files

- Cleaned up import statements in vscode.js and light-pastel.js by removing redundant lines for improved code clarity and maintainability.

* refactor: improve styling and theme integration in various components

- Added accent color and cursor style for checkbox inputs in Modal's StyledWrapper.
- Updated border-radius values in HttpMethodSelector and NewRequest StyledWrapper components to use theme variables for consistency.
- Introduced a new textbox class in NewRequest StyledWrapper for better styling control.
- Changed modal size from "sm" to "md" in CreateEnvironment for improved layout.

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-02 16:48:47 +05:30
naman-bruno
058d2e0e61 Improve delete collection in workspace overview (#6587)
* Improve delete collection in workspace overview

* fixes
2026-01-02 15:11:39 +05:30
gopu-bruno
c03c5eb927 feat: update toast UI to match theme (#6622) 2026-01-02 15:05:34 +05:30
Bijin A B
156e798f90 feat(theme): update secondary button styles (#6621) 2026-01-02 14:21:28 +05:30
Anoop M D
a1a90d19e8 feat: enhance HttpMethodSelector to include caret indicator when creating new request (#6620) 2026-01-02 04:27:06 +05:30
Anoop M D
ec40d7fc85 fix: update @opencollection/types to version 0.6.0 and refactor auth handling in request items (#6619) 2026-01-02 04:07:06 +05:30
gopu-bruno
5fda0866f8 feat: use theme colors for Console method badges (#6603)
* feat: use theme colors for Console method badges

* chore(theme): bruno devtools UX updates

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-02 04:06:55 +05:30
Sanjai Kumar
f611c05fe8 fix: update Notifications and StatusBar components for improved functionality and styling (#6607) 2026-01-02 02:41:05 +05:30
naman-bruno
b3764e1703 fix: invalid collection in workspace (#6612) 2026-01-02 01:21:44 +05:30
naman-bruno
f961e692ee add: global env and workspace flag (#6534)
* add: global env and workspace flag

* rm: await

* update: option name

* fix

* fix
2026-01-02 01:15:45 +05:30
Sanjai Kumar
a7237c2e41 feat: improve RunnerResults filter bar to use theme system (#6613)
* feat: integrate theme support in RunnerResults component for improved styling

* refactor: simplify RunnerResults component and enhance filter button styling

* style: adjust padding in StyledWrapper and remove aria-pressed from FilterButton
2026-01-02 01:13:23 +05:30
sanish chirayath
3ccaf29ddd fix: use theme styling within timeline (#6604)
* fix: use theme styling within timeline

* fix: remove inline styling and use css classes

* fix: network logs within dev tools

* compact timeline for grpc

* refactor: standardize CSS class naming in StyledWrapper components for better readability

* remove styling configuration from Network component

* fix: update colors

* update colors

* fix: color
2026-01-01 23:58:47 +05:30
Abhishek S Lal
002a5d16eb refactor: improve theme handling in ThemeProvider for better responsiveness to system preferences (#6606)
* refactor: improve theme handling in ThemeProvider for better responsiveness to system preferences

- Introduced helper functions to determine effective theme and apply it to the root element.
- Updated theme application logic to respond to system theme changes more efficiently.
- Simplified theme computation to avoid race conditions by directly using storedTheme.

* fix: update displayedTheme initialization in ThemeProvider to use storedTheme for consistency
2026-01-01 23:55:06 +05:30
gopu-bruno
877b4dcf3a fix: folder docs save button visibility issue (#6617) 2026-01-01 23:52:38 +05:30
sanish chirayath
b9856f8c64 Feat/update file picker (#6614)
* styling: file-picker editor component

* use filepicker component within filebody and response example filebody

* edit example to use button components

* fix: hide delete, disable checkbox in preview mode

* make label italic

* chore: change example cta buttons to filled style

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2026-01-01 23:47:58 +05:30
gopu-bruno
e63bac6ce4 fix: resolve request pane tooltip visibility issue (#6615) 2026-01-01 23:19:27 +05:30
Anoop M D
23e809e827 feat: improved dark mode color (#6616) 2026-01-01 22:06:38 +05:30
naman-bruno
1a4a30c8f2 add: beta tag for opencollection & fix create collection location behaviour (#6594)
* add: beta tag for opencollection

* fixes

* fix
2026-01-01 17:04:34 +05:30
naman-bruno
2c973bbd35 Merge pull request #6583 from naman-bruno/add/collection-docs
add: collection-docs
2026-01-01 17:01:31 +05:30
naman-bruno
8e74fa6233 fix: collection already opened in other workspace (#6574)
* fix: collection already opened in other workspace

* fix

* fixes
2026-01-01 14:30:26 +05:30
sanish chirayath
1ec8f55a9e fix: theme within grpc timeline (#6581)
* fix: theme within grpc timeline

* fix: use font from the theme

* remove y padding to make timeline item more compact

* fix: font

* fix: padding

* fix: use fira code

* fix: icon spacing

* add border to the method  search

* show bg for message section within request
2025-12-31 23:31:39 +05:30
Pooja
1ae05dfb0e fix: prefrence modal width (#6595) 2025-12-31 22:03:33 +05:30
gopu-bruno
72f186b38a style: update OAuth2 section labels for improved consistency and readability (#6598) 2025-12-31 21:50:22 +05:30
gopu-bruno
ea1002c7a0 fix: standardize table border colors and improve table styling (#6597) 2025-12-31 20:04:08 +05:30
gopu-bruno
89ed1da4de Fix auth panel UI updates (#6590)
* style: update padding and font size in OAuth2 and Table components for improved consistency

* style: update font styles in OAuth2 components for improved readability

* fix: add missing semicolon in StyledWrapper.js for consistent styling
2025-12-31 19:58:17 +05:30
lohit
4d519df8bc fix: oauth2 callback url field placeholder text update (#6588) 2025-12-31 18:20:02 +05:30
Sanjai Kumar
71413b9154 chore: update delete confirmation modals to use danger button color (#6589)
* refactor: remove size prop from Button components for consistency across modals and improve styling

* style: update confirm button colors in modal components for consistency
2025-12-31 18:13:33 +05:30
Chirag Chandrashekhar
ce9773b7c9 fix: Add New Request CTA alignment in tabs (#6584)
* fix: Add New Request CTA alignment in tabs
- Moved the '+' icon before the chevron to maintain alignment once chevrons appear
- Added padding to the '+' icon for better spacing.

* refactor: streamline New Request button rendering in RequestTabs component

- Simplified the rendering logic for the New Request button by removing unnecessary conditional wrappers.
- Ensured the button remains functional and maintains its styling within the tab layout.
2025-12-31 17:00:41 +05:30
Pooja
8a394cdafc remove max height for keybinding table (#6586) 2025-12-31 16:35:15 +05:30
Abhishek S Lal
ddc88b3b05 style: enhance CodeMirror dropdown styles with theme integration (#6577)
* style: enhance CodeMirror dropdown styles with theme integration for improved consistency

* style: refine dropdown and CodeMirror hint styles for improved consistency and usability

* style: clean up scrollbar and CodeMirror hint styles for improved readability
2025-12-31 15:10:16 +05:30
Abhishek S Lal
746a49faed style: enhance theme dropdown and security settings with improved styles and active indicators (#6582) 2025-12-31 14:29:52 +05:30
sanish chirayath
2827a6f133 fix: cookie modal theme (#6580)
* fix: cookie modal theme

* update toggle switch to use primary color
2025-12-31 13:38:53 +05:30
sanish chirayath
1ed9d61ee8 fix: radios buttons to use primary color (#6576) 2025-12-31 13:37:27 +05:30
sanish chirayath
ac0b69787d fix: use themes within protobuf section (#6575)
* fix: use themes within protobuf section

* chore: fix font weight for protobuf settings

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-31 13:36:41 +05:30
sanish chirayath
ab04850367 use themes within grpc (#6568)
* rm: redundant code

* add: theme to grpc

* keep same color for grpc as that of sidebar

* fix: checkbox color

* fix: use dropdown colors

* rm: redundant lines

* use: theme for grpc icons
2025-12-31 00:34:33 +05:30
sanish chirayath
1c9db0886d fix oauth checkbox, button styles (#6572)
* fix: checkboxes

* use secondary button
2025-12-31 00:00:58 +05:30
Abhishek S Lal
b75c9fdd6d style: update checkbox styles and colors in RunConfigurationPanel for improved visibility (#6570)
* style: update checkbox styles and colors in RunConfigurationPanel for improved visibility

* style: update RunnerResults component styles for configuration options and panels

* style: refine checkbox styles in StyledWrapper for consistency and clarity
2025-12-30 23:07:28 +05:30
naman-bruno
27dff7567c add: export info & remove unused component (#6547)
* add: export info

* changes
2025-12-30 23:06:57 +05:30
Bijin A B
8fa8ae5fed feat(sandbox): create a dropdown selector for sandbox mode (#6519) 2025-12-30 23:03:06 +05:30
Abhishek S Lal
0848393319 refactor: replace button elements with Button component for consistency (#6567)
* refactor: replace button elements with Button component for consistency and improved styling across various components

* chore: css cleanup

* chore: fix collection doc cancel button

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-30 21:06:10 +05:30
sanish chirayath
76f8bce9ac fix: oauth setup component colors (#6565)
* fix: oauth colors

* rm: redundant conditionals
2025-12-30 20:47:31 +05:30
sanish chirayath
676f8223ec fix: colors (#6564) 2025-12-30 20:29:43 +05:30
sanish chirayath
36b0a90de3 fix: use generic error component, tab padding fixes (#6563)
* fix: use generic error component, tab padding fixes

* fix: script error padding

* rename errorMessage to error banner, move to ui folder

* fix: replace errorAlert with errorBanner component

* show orange dot always
2025-12-30 18:48:53 +05:30
sanish chirayath
d7cef7aa4e fix: example tab not closing post delete, tab not found issue when i delete intermediate example (#6561) 2025-12-30 14:44:57 +05:30
gopu-bruno
5dad137631 fix: update secret var alert icon to use danger text color (#6562) 2025-12-30 14:33:57 +05:30
Abhishek S Lal
8b1d59fa74 feat: enhance ThemeDropdown with keyboard navigation and improved layout (#6554)
* feat: enhance ThemeDropdown with keyboard navigation and improved layout

- Added keyboard navigation support for theme selection.
- Refactored theme selection UI to improve accessibility and user experience.
- Updated styles for better responsiveness and visual clarity.
- Consolidated theme rendering logic for light and dark themes.

* style: update ThemeDropdown styles for focus and active states

- Removed outline styles for focused states to enhance visual clarity.
- Adjusted background and border colors for active state to improve accessibility and user experience.
2025-12-30 14:30:50 +05:30
Bijin A B
3a6f2f26ee chore: minor layout fixes (#6559) 2025-12-30 13:37:53 +05:30
naman-bruno
6f71717105 fix: error style for env & workspace rename (#6553) 2025-12-30 12:24:36 +05:30
Anoop M D
2b28d37c74 Merge pull request #6552 from usebruno/feat/update-primary-colors
feat: update primary colors in schema
2025-12-30 07:20:27 +05:30
Anoop M D
7675c1a4d8 feat: update primary colors in schema 2025-12-30 07:19:54 +05:30
Anoop M D
6afbaa0d91 feat: design system doc + cleanup codemirror info + collection overview icons (#6551) 2025-12-30 06:58:52 +05:30
naman-bruno
c0ac24d090 fix: default workspace docs (#6548) 2025-12-30 03:29:18 +05:30
Bijin A B
41b37c7805 feat(UX): update theme selector in preferences modal (#6550) 2025-12-30 03:23:24 +05:30
Anoop M D
6d77cacbc4 feat: dark mode input revamp (#6546) 2025-12-29 22:37:49 +05:30
Chirag Chandrashekhar
0d536fb365 refactor: simplify layout and styling in QueryUrl and HttpMethodSelector (#6545)
* refactor: simplify layout and styling in QueryUrl and HttpMethodSelector components; remove unused method selector width state

* chore: test fixes

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-29 22:09:39 +05:30
gopu-bruno
9a78432dc0 fix: unify hover and keyboard focus styles for collection component (#6544) 2025-12-29 20:57:15 +05:30
Abhishek S Lal
63d31825ff fix: update dropdown item text color to use theme text color for consistency; enhance ExampleItem dropdown behavior with fixed positioning (#6543) 2025-12-29 19:29:53 +05:30
gopu-bruno
018f39239f fix: replace hardcoded yellow text color with theme brand color (#6538) 2025-12-29 17:41:30 +05:30
Abhishek S Lal
1b57b6bee6 fix: initialize selected environment correctly to prevent flicker in EnvironmentSettings component; remove transition from StyledWrapper in RequestTabs and WorkspaceTabs (#6536) 2025-12-29 16:59:22 +05:30
naman-bruno
646f63dbeb add: filetype for import collection (#6533) 2025-12-29 16:53:43 +05:30
Anoop M D
c714e9b5d6 feat: default dark mode theme tweaks (#6531) 2025-12-29 06:13:56 +05:30
Bijin A B
f5ed96ad16 chore: reorder collection item menu option info (#6524) 2025-12-27 23:44:56 +05:30
Anoop M D
f40e4d2d79 feat: theme + ux overhaul (#6520)
* feat: theme + ux overhaul

* chore: moved @opencollection/types at root of monorepo

* chore: fixed review comments

* chore: addressed review comments

* chore: fix playwright tests

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-27 13:52:20 +05:30
Bijin A B
84f572fa88 chore: fix minor runtime warnings (#6518) 2025-12-27 00:17:14 +05:30
lohit
faec95f623 feat: collection-level and app-level proxy settings updates (#6514)
* feat: collection and app proxy settings updates

* fix: opencollection proxy config export and import

* fix: coderabbit review fixes
2025-12-26 22:17:41 +05:30
Sanjai Kumar
cd6ffc2447 refactor: replace button elements with new Button component (#6512)
* refactor: replace button elements with new Button component

* chore: fix button size for consistency

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-26 19:25:45 +05:30
Bijin A B
d37bf7a5ad chore: update responsive tab's more icon (#6509) 2025-12-26 17:11:56 +05:30
860 changed files with 50397 additions and 10169 deletions

View File

@@ -0,0 +1,70 @@
const fs = require('fs');
const { execSync } = require('child_process');
// Check if flaky-tests.json exists
if (!fs.existsSync('flaky-tests.json')) {
console.log('No flaky-tests.json found');
process.exit(0);
}
// Get changed files in PR
let changedFiles = [];
try {
changedFiles = execSync('git diff --name-only origin/main...HEAD')
.toString()
.split('\n')
.filter(f => f.endsWith('.spec.ts'));
} catch (error) {
console.log('Could not determine changed files:', error.message);
process.exit(0);
}
if (changedFiles.length === 0) {
console.log('No test files were modified in this PR');
process.exit(0);
}
// Read flaky tests
const flakyTests = JSON.parse(fs.readFileSync('flaky-tests.json', 'utf8'));
if (flakyTests.length === 0) {
console.log('No flaky/failed tests found');
process.exit(0);
}
// Find modified flaky tests
const modifiedFlakyTests = flakyTests.filter(test =>
changedFiles.some(file => test.file.includes(file))
);
if (modifiedFlakyTests.length === 0) {
console.log('No modified test files are flaky');
process.exit(0);
}
// Generate comment markdown
let comment = '## ⚠️ Warning: You modified flaky/failed test files\n\n';
comment += 'The following test files you modified have reliability issues:\n\n';
modifiedFlakyTests.forEach(test => {
const testType = test.status === 'failed' ? '❌ Failed' : '⚠️ Flaky';
comment += `### ${testType}: \`${test.file}\`\n`;
comment += `**Test:** ${test.testTitle}\n`;
comment += `**Status:** ${test.status}\n`;
if (test.retryAttempt > 0) {
comment += `**Retry Attempt:** ${test.retryAttempt}\n`;
}
comment += '\n**To debug locally, run:**\n';
comment += '```bash\n';
comment += `npx playwright test ${test.file} --repeat-each=5 --workers=1\n`;
comment += '```\n\n';
});
comment += '---\n';
comment += '**Note:** Flaky tests passed after retrying, failed tests did not pass. ';
comment += 'Please investigate and fix the root cause before merging.\n';
// Save comment to file for GitHub Action to post
fs.writeFileSync('pr-comment.md', comment);
console.log(`Found ${modifiedFlakyTests.length} modified flaky tests`);

78
.github/scripts/detect-flaky-tests.js vendored Normal file
View File

@@ -0,0 +1,78 @@
const fs = require('fs');
// Read Playwright JSON report
const resultsPath = 'playwright-report/results.json';
if (!fs.existsSync(resultsPath)) {
console.log('No Playwright results found at', resultsPath);
process.exit(0);
}
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
// Extract flaky tests
// A test is flaky if: status === "passed" AND retry > 0
// A test is failed if: status === "failed"
// This means it failed initially but passed on retry OR failed completely
const flakyTests = [];
function traverseSuites(suites) {
for (const suite of suites) {
// Process specs in this suite
for (const spec of suite.specs || []) {
for (const test of spec.tests || []) {
// Check each test result
for (const result of test.results || []) {
// Track two types of problematic tests:
// 1. Flaky: passed on a retry attempt (retry > 0)
// 2. Failed: failed on all attempts
if ((result.status === 'passed' && result.retry > 0) || result.status === 'failed') {
flakyTests.push({
file: spec.file,
title: spec.title,
testTitle: spec.title,
line: spec.line,
status: result.status,
retryAttempt: result.retry
});
break; // Only record once per test
}
}
}
}
// Recursively process nested suites
if (suite.suites && suite.suites.length > 0) {
traverseSuites(suite.suites);
}
}
}
traverseSuites(results.suites || []);
// Save flaky tests to JSON
fs.writeFileSync('flaky-tests.json', JSON.stringify(flakyTests, null, 2));
// Generate markdown report
let markdown = '## ⚠️ Flaky/Failed Tests Detected\n\n';
markdown += 'The following tests are problematic:\n\n';
flakyTests.forEach(test => {
const testType = test.status === 'failed' ? '❌ Failed' : '⚠️ Flaky';
markdown += `### ${testType}: \`${test.file}\`\n`;
markdown += `- **Test:** ${test.testTitle}\n`;
markdown += `- **Status:** ${test.status}\n`;
if (test.retryAttempt > 0) {
markdown += `- **Retry Attempt:** ${test.retryAttempt}\n`;
}
markdown += `- **Debug command:**\n`;
markdown += '```bash\n';
markdown += `npx playwright test ${test.file} --repeat-each=5 --workers=1\n`;
markdown += '```\n\n';
});
fs.writeFileSync('flaky-report.md', markdown);
console.log(`Found ${flakyTests.length} flaky/failed tests`);
process.exit(flakyTests.length > 0 ? 1 : 0);

View File

@@ -0,0 +1,119 @@
name: Flaky Test Detector
on:
pull_request:
branches: [main]
paths:
- 'tests/**/*.spec.ts'
permissions:
contents: read
pull-requests: write
checks: write
jobs:
detect-flaky-tests:
name: Detect Flaky Tests
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # Need full history to compare with main
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get --no-install-recommends install -y \
libglib2.0-0 libnss3 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libgtk-3-0 libasound2t64 xvfb
- name: Install npm dependencies
run: |
npm ci --legacy-peer-deps
sudo chown root /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
sudo chmod 4755 /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
- name: Install test collection dependencies
run: npm ci --prefix packages/bruno-tests/collection
- name: Build libraries
run: |
npm run build:graphql-docs
npm run build:bruno-query
npm run build:bruno-common
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
npm run build:bruno-converters
npm run build:bruno-requests
npm run build:schema-types
npm run build:bruno-filestore
- name: Run Playwright tests
run: xvfb-run npm run test:e2e
continue-on-error: true # Continue even if tests fail
- name: Detect flaky tests
id: detect
run: node .github/scripts/detect-flaky-tests.js
continue-on-error: true # Don't fail workflow if flaky tests found
- name: Check modified flaky tests
id: check-modified
run: node .github/scripts/comment-on-flaky-tests.js
continue-on-error: true
- name: Post PR comment
if: hashFiles('pr-comment.md') != ''
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const comment = fs.readFileSync('pr-comment.md', 'utf8');
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Warning: You modified flaky/failed test files')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Upload flaky test artifacts
if: always()
uses: actions/upload-artifact@v6
with:
name: flaky-test-results
path: |
flaky-tests.json
flaky-report.md
playwright-report/
retention-days: 30

View File

@@ -58,6 +58,8 @@ jobs:
run: npm run test --workspace=packages/bruno-converters
- name: Test Package bruno-electron
run: npm run test --workspace=packages/bruno-electron
- name: Test Package bruno-requests
run: npm run test --workspace=packages/bruno-requests
cli-test:
name: CLI Tests

2
.gitignore vendored
View File

@@ -48,12 +48,14 @@ yarn-error.log*
bruno.iml
.idea
.vscode
.cursor
# Playwright
/blob-report/
# Development plan files
CLAUDE.md
AGENTS.md
*.plan.md
# packages dist

View File

@@ -1,7 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120
}

View File

@@ -66,7 +66,16 @@ Remember, these rules are here to make our codebase harmonious. If something doe
- Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any react component using the styled component
- Styled Components are used as wrappers to define both self and children components style, tailwind classes are used specifically for layout based styles.
- Styled Component CSS might also change layout but tailwind classes shouldn't define colors.
- Styled Component CSS might also change layout but tailwind classes shouldn't define colors.
- MUST: Prefer custom hooks for business logic, data fetching, and side-effects.
- MUST: Avoid `useEffect` unless absolutely needed. Prefer derived state, event handlers.
- SHOULD: Memoize only when necessary (`useMemo`/`useCallback`), and prefer moving logic into hooks first.
- MUST: Do not use namespace access for hooks in app code (e.g., `React.useCallback`, `React.useMemo`, `React.useState`). Import hooks directly.
- Correct: `import { useCallback, useMemo, useState } from "react";`
- Avoid: `import * as React from "react";` then `React.useCallback(...)`
- Add `data-testid` to testable elements for Playwright
- Co-locate utilities that are truly component-specific next to the component, otherwise place shared items under a common folder
## Readability and Abstractions

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 KiB

After

Width:  |  Height:  |  Size: 584 KiB

View File

@@ -3,7 +3,7 @@
### برونو - بيئة تطوير مفتوحة المصدر لاستكشاف واختبار واجهات برمجة التطبيقات (APIs).
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### ব্রুনো - API অন্বেষণ এবং পরীক্ষা করার জন্য ওপেনসোর্স IDE।
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - 开源 IDE用于探索和测试 API。
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Opensource IDE zum Erkunden und Testen von APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE de código abierto para explorar y probar APIs.
[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Actividad de Commits](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### برونو یا Bruno - محیط توسعه متن باز برای تست و توسعه API ها
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE Opensource pour explorer et tester des APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Opensource IDE per esplorare e testare gli APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API の検証・動作テストのためのオープンソース IDE.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### ბრუნო - ღია წყაროების IDE API-ების შესწავლისა და ტესტირებისათვის.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API 탐색 및 테스트를 위한 오픈소스 IDE.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Open source IDE voor het verkennen en testen van API's.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Otwartoźródłowe IDE do eksploracji i testów APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE de código aberto para explorar e testar APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - Mediu integrat de dezvoltare cu sursă deschisă pentru explorarea și testarea API-urilor.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE.
[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - IDE із відкритим кодом для тестування та дослідження API
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

View File

@@ -3,7 +3,7 @@
### Bruno - 探索和測試 API 的開源 IDE 工具
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%2Fbruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)

767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,23 +20,23 @@
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "~0.7.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
"@storybook/builder-webpack5": "^10.1.10",
"@storybook/react": "^10.1.10",
"@storybook/react-webpack5": "^10.1.10",
"storybook": "^10.1.10",
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "0.3.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/jest": "^29.5.11",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.14.1",
"@typescript-eslint/parser": "^8.39.0",
"concurrently": "^8.2.2",
"cross-env": "10.1.0",
"eslint": "^9.26.0",
"eslint-plugin-diff": "^2.0.3",
"fs-extra": "^11.1.1",
@@ -49,19 +49,20 @@
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"rimraf": "^6.0.1",
"storybook": "^10.1.10",
"ts-jest": "^29.2.6"
},
"scripts": {
"setup": "node ./scripts/setup.js",
"watch:converters": "npm run watch --workspace=packages/bruno-converters",
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
"dev": "node ./scripts/dev.js",
"watch": "npm run dev:watch",
"dev:watch": "node ./scripts/dev-hot-reload.js",
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
"storybook": "npm run storybook --workspace=packages/bruno-app",
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
"build:bruno-requests": "npm run build --workspace=packages/bruno-requests",
"build:bruno-filestore": "npm run build --workspace=packages/bruno-filestore",
@@ -77,12 +78,12 @@
"build:electron:rpm": "./scripts/build-electron.sh rpm",
"build:electron:snap": "./scripts/build-electron.sh snap",
"watch:common": "npm run watch --workspace=packages/bruno-common",
"watch:requests": "npm run watch --workspace=packages/bruno-requests",
"test:codegen": "node playwright/codegen.ts",
"test:e2e": "playwright test --project=default",
"test:e2e:ssl": "playwright test --project=ssl",
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
"lint": "node --max_old_space_size=4096 $(npx which eslint)",
"lint:fix": "node --max_old_space_size=4096 $(npx which eslint) --fix",
"lint": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" npx eslint",
"lint:fix": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" npx eslint --fix",
"prepare": "husky"
},
"nano-staged": {
@@ -97,5 +98,8 @@
"json-schema-typed": "8.0.1"
}
}
},
"dependencies": {
"ajv": "^8.17.1"
}
}
}

View File

@@ -34,4 +34,5 @@ yarn-error.log*
.next/
dist/
.env
.env
storybook-static/

View File

@@ -1,7 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120
}

View File

@@ -8,8 +8,6 @@
"build": "rsbuild build -m production",
"preview": "rsbuild preview",
"test": "jest",
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\"",
"storybook": "storybook dev -p 6006 --config-dir storybook",
"build-storybook": "storybook build --config-dir storybook"
},
@@ -52,6 +50,7 @@
"json5": "^2.2.3",
"jsonc-parser": "^3.2.1",
"jsonpath-plus": "^10.3.0",
"jsonschema": "^1.5.0",
"know-your-http-well": "^0.5.0",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",
@@ -68,7 +67,7 @@
"polished": "^4.3.1",
"posthog-node": "4.2.1",
"prettier": "^2.7.1",
"qs": "^6.11.0",
"qs": "^6.14.1",
"query-string": "^7.0.1",
"react": "19.0.0",
"react-copy-to-clipboard": "^5.1.0",
@@ -83,6 +82,7 @@
"react-player": "^2.16.0",
"react-redux": "^7.2.9",
"react-tooltip": "^5.5.2",
"react-virtuoso": "^4.18.1",
"sass": "^1.46.0",
"semver": "^7.7.1",
"shell-quote": "^1.8.3",
@@ -130,4 +130,4 @@
"form-data": "4.0.4"
}
}
}
}

View File

@@ -55,10 +55,10 @@ const StyledWrapper = styled.div`
}
.cm-variable-valid {
color: green;
color: ${(props) => props.theme.codemirror.variable.valid};
}
.cm-variable-invalid {
color: red;
color: ${(props) => props.theme.codemirror.variable.invalid};
}
`;

View File

@@ -37,7 +37,7 @@ const FileEditor = ({ apiSpec }) => {
/>
<IconDeviceFloppy
onClick={onSave}
color={hasChanges ? theme.colors.text.yellow : theme.requestTabs.icon.color}
color={hasChanges ? theme.draftColor : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
className={`absolute right-0 top-0 m-4 ${

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
}
div.dropdown-item.menu-item {
color: ${(props) => props.theme.colors.danger};
color: ${(props) => props.theme.colors.text.danger};
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger};
color: white;

View File

@@ -0,0 +1,15 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
align-items: center;
height: 100%;
-webkit-app-region: no-drag;
.shortcut {
font-size: 11px;
color: ${(props) => props.theme.dropdown.mutedText};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,154 @@
import React, { useState } from 'react';
import { IconMenu2 } from '@tabler/icons';
import MenuDropdown from 'ui/MenuDropdown';
import ActionIcon from 'ui/ActionIcon';
import StyledWrapper from './StyledWrapper';
const AppMenu = () => {
const [isOpen, setIsOpen] = useState(false);
const { ipcRenderer } = window;
const menuItems = [
{
id: 'file',
label: 'File',
submenu: [
{
id: 'open-collection',
label: 'Open Collection',
onClick: () => ipcRenderer?.invoke('renderer:open-collection')
},
{ type: 'divider', id: 'file-div-1' },
{
id: 'preferences',
label: 'Preferences',
rightSection: <span className="shortcut">Ctrl+,</span>,
onClick: () => ipcRenderer?.invoke('renderer:open-preferences')
},
{ type: 'divider', id: 'file-div-2' },
{
id: 'quit',
label: 'Quit',
rightSection: <span className="shortcut">Alt+F4</span>,
onClick: () => ipcRenderer?.send('renderer:window-close')
}
]
},
{
id: 'edit',
label: 'Edit',
submenu: [
{
id: 'undo',
label: 'Undo',
rightSection: <span className="shortcut">Ctrl+Z</span>,
onClick: () => document.execCommand('undo')
},
{
id: 'redo',
label: 'Redo',
rightSection: <span className="shortcut">Ctrl+Y</span>,
onClick: () => document.execCommand('redo')
},
{ type: 'divider', id: 'edit-div-1' },
{
id: 'cut',
label: 'Cut',
rightSection: <span className="shortcut">Ctrl+X</span>,
onClick: () => document.execCommand('cut')
},
{
id: 'copy',
label: 'Copy',
rightSection: <span className="shortcut">Ctrl+C</span>,
onClick: () => document.execCommand('copy')
},
{
id: 'paste',
label: 'Paste',
rightSection: <span className="shortcut">Ctrl+V</span>,
onClick: () => document.execCommand('paste')
},
{ type: 'divider', id: 'edit-div-2' },
{
id: 'select-all',
label: 'Select All',
rightSection: <span className="shortcut">Ctrl+A</span>,
onClick: () => document.execCommand('selectAll')
}
]
},
{
id: 'view',
label: 'View',
submenu: [
{
id: 'toggle-devtools',
label: 'Developer Tools',
rightSection: <span className="shortcut">Ctrl+Shift+I</span>,
onClick: () => ipcRenderer?.invoke('renderer:toggle-devtools')
},
{ type: 'divider', id: 'view-div-1' },
{
id: 'reset-zoom',
label: 'Reset Zoom',
rightSection: <span className="shortcut">Ctrl+0</span>,
onClick: () => ipcRenderer?.invoke('renderer:reset-zoom')
},
{
id: 'zoom-in',
label: 'Zoom In',
rightSection: <span className="shortcut">Ctrl++</span>,
onClick: () => ipcRenderer?.invoke('renderer:zoom-in')
},
{
id: 'zoom-out',
label: 'Zoom Out',
rightSection: <span className="shortcut">Ctrl+-</span>,
onClick: () => ipcRenderer?.invoke('renderer:zoom-out')
},
{ type: 'divider', id: 'view-div-2' },
{
id: 'toggle-fullscreen',
label: 'Full Screen',
rightSection: <span className="shortcut">F11</span>,
onClick: () => ipcRenderer?.invoke('renderer:toggle-fullscreen')
}
]
},
{
id: 'help',
label: 'Help',
submenu: [
{
id: 'about',
label: 'About Bruno',
onClick: () => ipcRenderer?.invoke('renderer:open-about')
},
{
id: 'documentation',
label: 'Documentation',
onClick: () => ipcRenderer?.invoke('renderer:open-docs')
}
]
}
];
return (
<StyledWrapper>
<MenuDropdown
opened={isOpen}
onChange={setIsOpen}
placement="bottom-start"
showTickMark={false}
items={menuItems}
>
<ActionIcon label="Menu" size="lg">
<IconMenu2 size={16} stroke={1.5} />
</ActionIcon>
</MenuDropdown>
</StyledWrapper>
);
};
export default AppMenu;

View File

@@ -210,6 +210,10 @@ const Wrapper = styled.div`
margin-left: 6px;
}
.app-menu {
margin-left: 8px;
}
/* Custom window control buttons for Windows - always interactive, above modal overlay */
.window-controls {
display: flex;
@@ -246,12 +250,6 @@ const Wrapper = styled.div`
color: white;
}
}
.dropdown-item-active {
font-weight: 400 !important;
background-color: ${(props) => props.theme.dropdown.selectedBg} !important;
color: ${(props) => props.theme.dropdown.selectedColor} !important;
}
`;
export default Wrapper;

View File

@@ -17,10 +17,11 @@ import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
import ImportWorkspace from 'components/WorkspaceSidebar/ImportWorkspace';
import IconBottombarToggle from 'components/Icons/IconBottombarToggle/index';
import AppMenu from './AppMenu';
import StyledWrapper from './StyledWrapper';
import { toTitleCase } from 'utils/common/index';
import ResponseLayoutToggle from 'components/ResponsePane/ResponseLayoutToggle';
import { isMacOS, isWindowsOS, isLinuxOS } from 'utils/common/platform';
import classNames from 'classnames';
const getOsClass = () => {
if (isMacOS()) return 'os-mac';
@@ -29,6 +30,12 @@ const getOsClass = () => {
return 'os-other';
};
// Helper to get display name for workspace
export const getWorkspaceDisplayName = (name) => {
if (!name) return 'Untitled Workspace';
return name;
};
const AppTitleBar = () => {
const dispatch = useDispatch();
const [isFullScreen, setIsFullScreen] = useState(false);
@@ -115,7 +122,7 @@ const AppTitleBar = () => {
const WorkspaceName = forwardRef((props, ref) => {
return (
<div ref={ref} className="workspace-name-container" {...props}>
<span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
<span data-testid="workspace-name" className={classNames('workspace-name', { 'italic text-muted': !activeWorkspace?.name })}>{getWorkspaceDisplayName(activeWorkspace?.name)}</span>
<IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
</div>
);
@@ -127,7 +134,7 @@ const AppTitleBar = () => {
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
toast.success(`Switched to ${getWorkspaceDisplayName(workspaces.find((w) => w.uid === workspaceUid)?.name)}`);
};
const handleOpenWorkspace = async () => {
@@ -178,7 +185,7 @@ const AppTitleBar = () => {
return {
id: workspace.uid,
label: toTitleCase(workspace.name),
label: getWorkspaceDisplayName(workspace.name),
onClick: () => handleWorkspaceSwitch(workspace.uid),
className: `workspace-item ${isActive ? 'active' : ''}`,
rightSection: (
@@ -190,11 +197,7 @@ const AppTitleBar = () => {
label={isPinned ? 'Unpin workspace' : 'Pin workspace'}
size="sm"
>
{isPinned ? (
<IconPinned size={14} stroke={1.5} />
) : (
<IconPin size={14} stroke={1.5} />
)}
{isPinned ? <IconPinned size={14} stroke={1.5} /> : <IconPin size={14} stroke={1.5} />}
</ActionIcon>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
@@ -245,14 +248,10 @@ const AppTitleBar = () => {
)}
<div className="titlebar-content">
{/* Left section: Home + Workspace */}
<div className="titlebar-left">
<ActionIcon
onClick={handleHomeClick}
label="Home"
size="lg"
className="home-button"
>
{showWindowControls && <AppMenu />}
<ActionIcon onClick={handleHomeClick} label="Home" size="lg" className="home-button">
<IconHome size={16} stroke={1.5} />
</ActionIcon>

View File

@@ -19,7 +19,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.primary.text};
}
.dropdown-icon {

View File

@@ -1,4 +1,5 @@
import React, { useMemo } from 'react';
import get from 'lodash/get';
import CodeEditor from 'components/CodeEditor';
import { useTheme } from 'providers/Theme';
import { useSelector } from 'react-redux';
@@ -21,7 +22,8 @@ const BulkEditor = ({ params, onChange, onToggle, onSave, onRun }) => {
<CodeEditor
mode="text/plain"
theme={displayedTheme}
font={preferences.codeFont || 'default'}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={parsedParams}
onEdit={handleEdit}
onSave={onSave}

View File

@@ -36,12 +36,12 @@ const StyledWrapper = styled.div`
/* Style line numbers when there's a lint issue */
.CodeMirror-lint-line-error .CodeMirror-linenumber {
color: #d32f2f !important;
color: ${(props) => props.theme.colors.text.danger} !important;
text-decoration: underline;
}
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
color: #f57c00 !important;
color: ${(props) => props.theme.colors.text.warning} !important;
text-decoration: underline;
}
@@ -116,7 +116,7 @@ const StyledWrapper = styled.div`
span.cm-atom {
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
}
span.cm-variable {
span.cm-variable, span.cm-variable-2 {
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
}
span.cm-keyword {
@@ -128,14 +128,20 @@ const StyledWrapper = styled.div`
span.cm-operator {
color: ${(props) => props.theme.codemirror.tokens.operator} !important;
}
span.cm-tag {
color: ${(props) => props.theme.codemirror.tokens.tag} !important;
}
span.cm-tag.cm-bracket {
color: ${(props) => props.theme.codemirror.tokens.tagBracket} !important;
}
}
/* Variable validation colors */
.cm-variable-valid {
color: #5fad89 !important; /* Soft sage */
color: ${(props) => props.theme.codemirror.variable.valid} !important;
}
.cm-variable-invalid {
color: #d17b7b !important; /* Soft coral */
color: ${(props) => props.theme.codemirror.variable.invalid} !important;
}
.CodeMirror-search-hint {

View File

@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import React, { createRef } from 'react';
import { isEqual, escapeRegExp } from 'lodash';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import { setupAutoComplete } from 'utils/codemirror/autocomplete';
import { setupAutoComplete, showRootHints } from 'utils/codemirror/autocomplete';
import StyledWrapper from './StyledWrapper';
import * as jsonlint from '@prantlf/jsonlint';
import { JSHINT } from 'jshint';
@@ -16,7 +16,7 @@ import stripJsonComments from 'strip-json-comments';
import { getAllVariables } from 'utils/collections';
import { setupLinkAware } from 'utils/codemirror/linkAware';
import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors';
import CodeMirrorSearch from 'components/CodeMirrorSearch';
import CodeMirrorSearch from 'components/CodeMirrorSearch/index';
const CodeMirror = require('codemirror');
window.jsonlint = jsonlint;
@@ -34,6 +34,7 @@ export default class CodeEditor extends React.Component {
this.cachedValue = props.value || '';
this.variables = {};
this.searchResultsCountElementId = 'search-results-count';
this.searchBarRef = createRef();
this.lintOptions = {
esversion: 11,
@@ -94,14 +95,14 @@ export default class CodeEditor extends React.Component {
}
},
'Cmd-F': (cm) => {
if (!this.state.searchBarVisible) {
this.setState({ searchBarVisible: true });
}
this.setState({ searchBarVisible: true }, () => {
this.searchBarRef.current?.focus();
});
},
'Ctrl-F': (cm) => {
if (!this.state.searchBarVisible) {
this.setState({ searchBarVisible: true });
}
this.setState({ searchBarVisible: true }, () => {
this.searchBarRef.current?.focus();
});
},
'Cmd-H': 'replace',
'Ctrl-H': 'replace',
@@ -111,8 +112,12 @@ export default class CodeEditor extends React.Component {
: cm.replaceSelection(' ', 'end');
},
'Shift-Tab': 'indentLess',
'Ctrl-Space': 'autocomplete',
'Cmd-Space': 'autocomplete',
'Ctrl-Space': (cm) => {
showRootHints(cm, this.props.showHintsFor);
},
'Cmd-Space': (cm) => {
showRootHints(cm, this.props.showHintsFor);
},
'Ctrl-Y': 'foldAll',
'Cmd-Y': 'foldAll',
'Ctrl-I': 'unfoldAll',
@@ -228,10 +233,17 @@ export default class CodeEditor extends React.Component {
CodeMirror.signal(this.editor, 'change', this.editor);
}
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
const cursor = this.editor.getCursor();
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
this.editor.setCursor(cursor);
// TODO: temporary fix for keeping cursor state when auto save and new line insertion collide PR#7098
const nextValue = this.props.value ?? '';
const currentValue = this.editor.getValue();
if (this.editor.hasFocus?.() && currentValue !== nextValue) {
this.cachedValue = currentValue;
} else {
const cursor = this.editor.getCursor();
this.cachedValue = nextValue;
this.editor.setValue(nextValue);
this.editor.setCursor(cursor);
}
}
if (this.editor) {
@@ -305,6 +317,10 @@ export default class CodeEditor extends React.Component {
fontSize={this.props.fontSize}
>
<CodeMirrorSearch
ref={(node) => {
if (!node) return;
this.searchBarRef.current = node;
}}
visible={this.state.searchBarVisible}
editor={this.editor}
onClose={() => this.setState({ searchBarVisible: false })}

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.bruno-search-bar {
@@ -9,15 +10,15 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
flex-wrap: nowrap;
padding: 0 2px;
min-height: 36px;
background: ${(props) => props.theme.sidebar.search.bg} !important;
border-radius: 4px;
border: 1px solid ${(props) => props.theme.sidebar.search.bg} !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
gap: 0;
padding: 1px 3px;
width: auto;
min-width: 180px;
max-width: 320px;
min-height: 22px;
background: ${(props) => props.theme.background.base};
color: ${(props) => props.theme.text.base};
border: solid 1px ${(props) => props.theme.border.border2};
border-radius: ${(props) => props.theme.border.radius.sm};
}
.bruno-search-bar input {
@@ -38,7 +39,7 @@ const StyledWrapper = styled.div`
padding: 0 1px;
margin: 0 1px;
cursor: pointer;
color: #aaa;
color: ${(props) => props.theme.colors.text.subtext1};
border-radius: 3px;
height: 18px;
width: 18px;
@@ -51,27 +52,14 @@ const StyledWrapper = styled.div`
min-width: 28px;
text-align: center;
font-size: ${(props) => props.theme.font.size.xs};
color: #aaa;
color: ${(props) => props.theme.colors.text.subtext1};
margin: 0 8px 0 1px;
white-space: nowrap;
}
.bruno-search-bar.compact {
background: ${(props) => props.theme.codemirror.bg};
color: ${(props) => props.theme.codemirror.text || props.theme.text};
border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border-radius: 4px;
padding: 1px 3px;
min-height: 22px;
display: flex;
align-items: center;
gap: 0;
}
.bruno-search-bar input {
background: transparent;
color: inherit;
color: ${(props) => props.theme.colors.text.subtext2};
border: none;
outline: none;
font-size: ${(props) => props.theme.font.size.base};
@@ -92,7 +80,9 @@ const StyledWrapper = styled.div`
}
.searchbar-icon-btn.active {
color: #f39c12 !important;
color: ${(props) => props.theme.brand};
background-color: ${(props) => rgba(props.theme.brand, 0.1)};
font-weight: 500;
}
`;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import React, { useState, useEffect, useRef, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';
import { IconRegex, IconArrowUp, IconArrowDown, IconX, IconLetterCase, IconLetterW } from '@tabler/icons';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
@@ -8,7 +8,7 @@ function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');
}
const CodeMirrorSearch = ({ visible, editor, onClose }) => {
const CodeMirrorSearch = forwardRef(({ visible, editor, onClose }, ref) => {
const [searchText, setSearchText] = useState('');
const [regex, setRegex] = useState(false);
const [caseSensitive, setCaseSensitive] = useState(false);
@@ -19,6 +19,7 @@ const CodeMirrorSearch = ({ visible, editor, onClose }) => {
const searchMarks = useRef([]);
const searchLineHighlight = useRef(null);
const searchMatches = useRef([]);
const inputRef = useRef(null);
const debouncedSearchText = useDebounce(searchText, 150);
@@ -106,6 +107,14 @@ const CodeMirrorSearch = ({ visible, editor, onClose }) => {
}
}, [debouncedSearchText, regex, caseSensitive, wholeWord, editor, memoizedMatches]);
useImperativeHandle(ref, () => ({
focus: () => {
if (inputRef.current) {
inputRef.current.focus();
}
}
}));
useEffect(() => {
doSearch(0, debouncedSearchText);
}, [debouncedSearchText, doSearch]);
@@ -166,8 +175,9 @@ const CodeMirrorSearch = ({ visible, editor, onClose }) => {
return (
<StyledWrapper>
<div className="bruno-search-bar compact">
<div className="bruno-search-bar">
<input
ref={inputRef}
autoFocus
type="text"
value={searchText}
@@ -196,6 +206,6 @@ const CodeMirrorSearch = ({ visible, editor, onClose }) => {
</div>
</StyledWrapper>
);
};
});
export default CodeMirrorSearch;

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {
@@ -13,7 +14,8 @@ const Wrapper = styled.div`
}
.auth-placement-selector {
padding: 0.5rem 0px;
font-size: ${(props) => props.theme.font.size.sm};
padding: 0.2rem 0px;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
@@ -37,7 +39,6 @@ const Wrapper = styled.div`
.auth-type-label {
width: fit-content;
color: ${(props) => props.theme.colors.text.yellow};
justify-content: space-between;
padding: 0 0.5rem;
}

View File

@@ -57,29 +57,31 @@ const ApiKeyAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Key</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Key</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={apikeyAuth.key || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAuthChange('key', val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Value</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Value</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={apikeyAuth.value || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAuthChange('value', val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Add To</label>
<label className="block mb-1">Add To</label>
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div

View File

@@ -7,7 +7,7 @@ const Wrapper = styled.div`
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.primary.text};
.caret {
color: rgb(140, 140, 140);

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -123,19 +123,20 @@ const AwsV4Auth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Access Key ID</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Access Key ID</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={awsv4Auth.accessKeyId || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAccessKeyIdChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-2 flex items-center">
<label className="block mb-1">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-3 flex items-center">
<SingleLineEditor
value={awsv4Auth.secretAccessKey || ''}
theme={storedTheme}
@@ -143,51 +144,56 @@ const AwsV4Auth = ({ collection }) => {
onChange={(val) => handleSecretAccessKeyChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="awsv4-secret-access-key" warningMessage={warningMessage} />}
</div>
<label className="block font-medium mb-2">Session Token</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Session Token</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={awsv4Auth.sessionToken || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSessionTokenChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Service</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Service</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={awsv4Auth.service || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleServiceChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Region</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Region</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={awsv4Auth.region || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleRegionChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Profile Name</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Profile Name</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={awsv4Auth.profileName || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleProfileNameChange(val)}
collection={collection}
isCompact
/>
</div>
</StyledWrapper>

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -47,18 +47,19 @@ const BasicAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={basicAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Password</label>
<label className="block mb-1">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={basicAuth.password || ''}
@@ -67,6 +68,7 @@ const BasicAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="basic-password" warningMessage={warningMessage} />}
</div>

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -33,7 +33,7 @@ const BearerAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Token</label>
<label className="block mb-1">Token</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={bearerToken}
@@ -42,6 +42,7 @@ const BearerAuth = ({ collection }) => {
onChange={(val) => handleTokenChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="bearer-token" warningMessage={warningMessage} />}
</div>

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -47,18 +47,19 @@ const DigestAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={digestAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Password</label>
<label className="block mb-1">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={digestAuth.password || ''}
@@ -67,6 +68,7 @@ const DigestAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="digest-password" warningMessage={warningMessage} />}
</div>

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -64,19 +64,20 @@ const NTLMAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={ntlmAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<label className="block mb-1">Password</label>
<div className="single-line-editor-wrapper mb-3 flex items-center">
<SingleLineEditor
value={ntlmAuth.password || ''}
theme={storedTheme}
@@ -84,11 +85,12 @@ const NTLMAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="ntlm-password" warningMessage={warningMessage} />}
</div>
<label className="block font-medium mb-2">Domain</label>
<label className="block mb-1">Domain</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.domain || ''}
@@ -96,6 +98,7 @@ const NTLMAuth = ({ collection }) => {
onSave={handleSave}
onChange={(val) => handleDomainChange(val)}
collection={collection}
isCompact
/>
</div>
</StyledWrapper>

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: ${(props) => props.theme.font.size.base};
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -47,18 +47,19 @@ const WsseAuth = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<label className="block mb-1">Username</label>
<div className="single-line-editor-wrapper mb-3">
<SingleLineEditor
value={wsseAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUserChange(val)}
collection={collection}
isCompact
/>
</div>
<label className="block font-medium mb-2">Password</label>
<label className="block mb-1">Password</label>
<div className="single-line-editor-wrapper flex items-center">
<SingleLineEditor
value={wsseAuth.password || ''}
@@ -67,6 +68,7 @@ const WsseAuth = ({ collection }) => {
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
isCompact
/>
{showWarning && <SensitiveFieldWarning fieldName="wsse-password" warningMessage={warningMessage} />}
</div>

View File

@@ -30,11 +30,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -9,6 +9,8 @@ import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
import Button from 'ui/Button/index';
import ActionIcon from 'ui/ActionIcon/index';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
@@ -55,17 +57,17 @@ const Docs = ({ collection }) => {
<div className="flex flex-row gap-2 items-center justify-center">
{isEditing ? (
<>
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
<IconX className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
<Button type="button" color="secondary" onClick={handleDiscardChanges}>
Cancel
</Button>
<Button type="button" onClick={onSave}>
Save
</button>
</Button>
</>
) : (
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
<ActionIcon className="editing-mode" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={16} strokeWidth={1.5} />
</ActionIcon>
)}
</div>
</div>

View File

@@ -11,6 +11,7 @@ import { headers as StandardHTTPHeaders } from 'know-your-http-well';
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
import BulkEditor from 'components/BulkEditor/index';
import Button from 'ui/Button';
import { headerNameRegex, headerValueRegex } from 'utils/common/regex';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
@@ -32,6 +33,22 @@ const Headers = ({ collection }) => {
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const getRowError = useCallback((row, index, key) => {
if (key === 'name') {
if (!row.name || row.name.trim() === '') return null;
if (!headerNameRegex.test(row.name)) {
return 'Header name cannot contain spaces or newlines';
}
}
if (key === 'value') {
if (!row.value) return null;
if (!headerValueRegex.test(row.value)) {
return 'Header value cannot contain newlines';
}
}
return null;
}, []);
const columns = [
{
key: 'name',
@@ -39,7 +56,7 @@ const Headers = ({ collection }) => {
isKeyField: true,
placeholder: 'Name',
width: '30%',
render: ({ row, value, onChange, isLastEmptyRow }) => (
render: ({ value, onChange }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
@@ -47,7 +64,7 @@ const Headers = ({ collection }) => {
onChange={(newValue) => onChange(newValue.replace(/[\r\n]/g, ''))}
autocomplete={headerAutoCompleteList}
collection={collection}
placeholder={isLastEmptyRow ? 'Name' : ''}
placeholder={!value ? 'Name' : ''}
/>
)
},
@@ -55,7 +72,7 @@ const Headers = ({ collection }) => {
key: 'value',
name: 'Value',
placeholder: 'Value',
render: ({ row, value, onChange, isLastEmptyRow }) => (
render: ({ value, onChange }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
@@ -63,7 +80,7 @@ const Headers = ({ collection }) => {
onChange={onChange}
collection={collection}
autocomplete={MimeTypes}
placeholder={isLastEmptyRow ? 'Value' : ''}
placeholder={!value ? 'Value' : ''}
/>
)
}
@@ -101,6 +118,7 @@ const Headers = ({ collection }) => {
rows={headers}
onChange={handleHeadersChange}
defaultRow={defaultRow}
getRowError={getRowError}
/>
<div className="flex justify-end mt-2">
<button className="text-link select-none" onClick={toggleBulkEditMode}>

View File

@@ -0,0 +1,53 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.icon-box {
&.location {
background-color: ${(props) => rgba(props.theme.textLink, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.textLink, 0.09)};
svg {
color: ${(props) => props.theme.textLink};
}
}
&.environments {
background-color: ${(props) => rgba(props.theme.colors.text.green, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.colors.text.green, 0.09)};
svg {
color: ${(props) => props.theme.colors.text.green};
}
}
&.requests {
background-color: ${(props) => rgba(props.theme.colors.text.purple, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.colors.text.purple, 0.09)};
svg {
color: ${(props) => props.theme.colors.text.purple};
}
}
&.share {
background-color: ${(props) => rgba(props.theme.textLink, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.textLink, 0.09)};
svg {
color: ${(props) => props.theme.textLink};
}
}
&.generate-docs {
background-color: ${(props) => rgba(props.theme.accents.primary, 0.08)};
border: 1px solid ${(props) => rgba(props.theme.accents.primary, 0.09)};
svg {
color: ${(props) => props.theme.accents.primary};
}
}
}
`;
export default StyledWrapper;

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { getTotalRequestCountInCollection } from 'utils/collections/';
import { IconFolder, IconWorld, IconApi, IconShare } from '@tabler/icons';
import { IconFolder, IconWorld, IconApi, IconShare, IconBook } from '@tabler/icons';
import { areItemsLoading, getItemsLoadStats } from 'utils/collections/index';
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import ShareCollection from 'components/ShareCollection/index';
import GenerateDocumentation from 'components/Sidebar/Collections/Collection/GenerateDocumentation';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import StyledWrapper from './StyledWrapper';
const Info = ({ collection }) => {
const dispatch = useDispatch();
@@ -14,6 +16,7 @@ const Info = ({ collection }) => {
const isCollectionLoading = areItemsLoading(collection);
const { loading: itemsLoadingCount, total: totalItems } = getItemsLoadStats(collection);
const [showShareCollectionModal, toggleShowShareCollectionModal] = useState(false);
const [showGenerateDocumentationModal, setShowGenerateDocumentationModal] = useState(false);
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
@@ -25,17 +28,17 @@ const Info = ({ collection }) => {
};
return (
<div className="w-full flex flex-col h-fit">
<StyledWrapper className="w-full flex flex-col h-fit">
<div className="rounded-lg py-6">
<div className="grid gap-5">
{/* Location Row */}
<div className="flex items-start">
<div className="flex-shrink-0 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<IconFolder className="w-5 h-5 text-blue-500" stroke={1.5} />
<div className="icon-box location flex-shrink-0 p-3 rounded-lg">
<IconFolder className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium">Location</div>
<div className="mt-1 text-muted break-all text-xs">
<div className="mt-1 text-muted break-all">
{collection.pathname}
</div>
</div>
@@ -43,15 +46,15 @@ const Info = ({ collection }) => {
{/* Environments Row */}
<div className="flex items-start">
<div className="flex-shrink-0 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<IconWorld className="w-5 h-5 text-green-500" stroke={1.5} />
<div className="icon-box environments flex-shrink-0 p-3 rounded-lg">
<IconWorld className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium text-sm">Environments</div>
<div className="font-medium">Environments</div>
<div className="mt-1 flex flex-col gap-1">
<button
type="button"
className="text-sm text-link cursor-pointer hover:underline text-left bg-transparent"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
@@ -66,7 +69,7 @@ const Info = ({ collection }) => {
</button>
<button
type="button"
className="text-sm text-link cursor-pointer hover:underline text-left bg-transparent"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
@@ -85,12 +88,12 @@ const Info = ({ collection }) => {
{/* Requests Row */}
<div className="flex items-start">
<div className="flex-shrink-0 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<IconApi className="w-5 h-5 text-purple-500" stroke={1.5} />
<div className="icon-box requests flex-shrink-0 p-3 rounded-lg">
<IconApi className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4">
<div className="font-medium">Requests</div>
<div className="mt-1 text-muted text-xs">
<div className="mt-1 text-muted">
{
isCollectionLoading ? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
}
@@ -99,20 +102,33 @@ const Info = ({ collection }) => {
</div>
<div className="flex items-start group cursor-pointer" onClick={handleToggleShowShareCollectionModal(true)}>
<div className="flex-shrink-0 p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
<IconShare className="w-5 h-5 text-indigo-500" stroke={1.5} />
<div className="icon-box share flex-shrink-0 p-3 rounded-lg">
<IconShare className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4 h-full flex flex-col justify-start">
<div className="font-medium h-fit my-auto">Share</div>
<div className="group-hover:underline text-link text-xs">
<div className="group-hover:underline text-link">
Share Collection
</div>
</div>
</div>
{showShareCollectionModal && <ShareCollection collectionUid={collection.uid} onClose={handleToggleShowShareCollectionModal(false)} />}
<div className="flex items-start group cursor-pointer" onClick={() => setShowGenerateDocumentationModal(true)}>
<div className="icon-box generate-docs flex-shrink-0 p-3 rounded-lg">
<IconBook className="w-5 h-5" stroke={1.5} />
</div>
<div className="ml-4 h-full flex flex-col justify-start">
<div className="font-medium h-fit my-auto">Documentation</div>
<div className="group-hover:underline text-link">
Generate Docs
</div>
</div>
</div>
{showGenerateDocumentationModal && <GenerateDocumentation collectionUid={collection.uid} onClose={() => setShowGenerateDocumentationModal(false)} />}
</div>
</div>
</div>
</StyledWrapper>
);
};

View File

@@ -1,22 +1,29 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
&.card {
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
.title {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-top: 1px solid ${(props) => props.theme.table.border};
border-left: 1px solid ${(props) => props.theme.table.border};
border-right: 1px solid ${(props) => props.theme.table.border};
border-top-left-radius: 3px;
border-top-right-radius: 3px;
background-color: ${(props) => props.theme.status.warning.background};
}
.warning-icon {
color: ${(props) => props.theme.status.warning.text};
}
.table {
thead {
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
color: ${(props) => props.theme.table.thead.color} !important;
background: ${(props) => props.theme.sidebar.bg};
}
}
}

View File

@@ -41,8 +41,8 @@ const RequestsNotLoaded = ({ collection }) => {
return (
<StyledWrapper className="w-full card my-2">
<div className="flex items-center gap-2 px-3 py-2 title bg-yellow-50 dark:bg-yellow-900/20">
<IconAlertTriangle size={16} className="text-yellow-500" />
<div className="flex items-center gap-2 px-3 py-2 title">
<IconAlertTriangle size={16} className="warning-icon" />
<span className="font-medium">Following requests were not loaded</span>
</div>
<table className="w-full border-collapse">

View File

@@ -16,11 +16,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -36,7 +36,7 @@ const PresetsSettings = ({ collection }) => {
return (
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 mt-4 text-muted">
<div className="text-xs mb-4 text-muted">
These presets will be used as the default values for new requests in this collection.
</div>
<div className="bruno-form">

View File

@@ -8,6 +8,151 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.danger};
}
}
/* Section labels */
label {
color: ${(props) => props.theme.text};
}
/* Tooltip icon */
.tooltip-icon {
color: ${(props) => props.theme.colors.text.muted};
cursor: pointer;
}
/* Error messages */
.error-message {
color: ${(props) => props.theme.colors.text.danger};
background-color: ${(props) => props.theme.bg};
border-radius: ${(props) => props.theme.border.radius.base};
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
thead {
th {
text-align: left;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${(props) => props.theme.table.thead.color};
border: 1px solid ${(props) => props.theme.table.border};
padding: 0.5rem 0.75rem;
&.text-right {
text-align: right;
}
}
}
tbody {
td {
border: 1px solid ${(props) => props.theme.table.border};
padding: 0.5rem 0.75rem;
&.text-center {
text-align: center;
}
&.text-right {
text-align: right;
}
}
}
}
/* File/Directory icons */
.file-icon,
.folder-icon {
color: ${(props) => props.theme.colors.text.muted};
}
/* File/Directory names */
.file-name,
.directory-name {
font-weight: 500;
color: ${(props) => props.theme.text};
}
/* Path text */
.path-text {
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
font-family: monospace;
}
/* Empty state */
.empty-state {
.empty-icon {
color: ${(props) => props.theme.colors.text.muted};
}
.empty-text {
color: ${(props) => props.theme.colors.text.muted};
}
}
/* Invalid file indicator */
.invalid-indicator {
color: ${(props) => props.theme.colors.text.danger};
}
/* Action buttons */
.action-button {
padding: 0.25rem;
border-radius: ${(props) => props.theme.border.radius.base};
transition: all 0.2s;
&.replace-button {
color: ${(props) => props.theme.colors.text.danger};
&:hover {
color: ${(props) => props.theme.colors.text.danger};
background-color: ${(props) => props.theme.colors.bg.danger}20;
}
}
&.remove-button {
color: ${(props) => props.theme.colors.text.muted};
&:hover {
color: ${(props) => props.theme.text};
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
}
}
/* Checkbox */
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.colors.accent};
border-color: ${(props) => props.theme.table.border};
&:focus {
outline: none;
border-color: ${(props) => props.theme.primary.solid};
}
}
/* Add button */
.btn-add-param {
color: ${(props) => props.theme.textLink};
padding-right: 0.5rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
margin-top: 0.5rem;
user-select: none;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: ${(props) => props.theme.primary.solid};
}
}
`;
export default StyledWrapper;

View File

@@ -113,12 +113,12 @@ const ProtobufSettings = ({ collection }) => {
<div className="mb-6" data-testid="protobuf-proto-files-section">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center">
<label className="font-medium flex items-center" htmlFor="protoFiles">
<label className="flex items-center" htmlFor="protoFiles">
Proto Files (
{protoFiles.length}
)
<span id="proto-files-tooltip" className="ml-2">
<IconAlertCircle size={16} className="text-gray-500 cursor-pointer" />
<IconAlertCircle size={16} className="tooltip-icon" />
</span>
<Tooltip
anchorId="proto-files-tooltip"
@@ -131,7 +131,7 @@ const ProtobufSettings = ({ collection }) => {
<div>
{protoFiles.some((file) => !file.exists) && (
<div className="text-xs text-red-600 dark:text-red-400 mb-2 flex items-center p-2 rounded" data-testid="protobuf-invalid-files-message">
<div className="error-message text-xs mb-2 flex items-center p-2" data-testid="protobuf-invalid-files-message">
<IconAlertCircle size={14} className="mr-1" />
Some proto files cannot be found. Use the replace option to update their locations.
</div>
@@ -140,13 +140,13 @@ const ProtobufSettings = ({ collection }) => {
<table className="w-full border-collapse" data-testid="protobuf-proto-files-table">
<thead>
<tr>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th>
File
</th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th>
Path
</th>
<th className="text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th className="text-right">
Actions
</th>
</tr>
@@ -154,10 +154,10 @@ const ProtobufSettings = ({ collection }) => {
<tbody>
{protoFiles.length === 0 ? (
<tr>
<td colSpan="3" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
<div className="flex flex-col items-center">
<IconFile size={24} className="text-gray-400 mb-2" />
<span className="text-gray-500 dark:text-gray-400">No proto files added</span>
<td colSpan="3" className="text-center">
<div className="empty-state flex flex-col items-center">
<IconFile size={24} className="empty-icon mb-2" />
<span className="empty-text">No proto files added</span>
</div>
</td>
</tr>
@@ -167,27 +167,27 @@ const ProtobufSettings = ({ collection }) => {
return (
<tr key={index}>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<td>
<div className="flex items-center">
<IconFile size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
<span className="font-medium text-gray-900 dark:text-gray-100" data-testid="protobuf-proto-file-name">
<IconFile size={16} className="file-icon mr-2" />
<span className="file-name" data-testid="protobuf-proto-file-name">
{getBasename(collection.pathname, file.path)}
</span>
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
{!isValid && <IconAlertCircle size={12} className="invalid-indicator ml-2" />}
</div>
</td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono">
<td>
<div className="path-text">
{file.path}
</div>
</td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2 text-right">
<td className="text-right">
<div className="flex items-center justify-end space-x-1">
{!isValid && (
<button
type="button"
onClick={() => handleReplaceProtoFile(index)}
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 p-1 rounded"
className="action-button replace-button"
title="Replace file"
>
<IconFileImport size={14} />
@@ -196,7 +196,7 @@ const ProtobufSettings = ({ collection }) => {
<button
type="button"
onClick={() => handleRemoveProtoFile(index)}
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 p-1 rounded"
className="action-button remove-button"
title="Remove file"
data-testid="protobuf-remove-file-button"
>
@@ -220,12 +220,12 @@ const ProtobufSettings = ({ collection }) => {
<div className="mb-6" data-testid="protobuf-import-paths-section">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center">
<label className="font-medium flex items-center" htmlFor="importPaths">
<label className="flex items-center" htmlFor="importPaths">
Import Paths (
{importPaths.length}
)
<span id="import-paths-tooltip" className="ml-2">
<IconAlertCircle size={16} className="text-gray-500 cursor-pointer" />
<IconAlertCircle size={16} className="tooltip-icon" />
</span>
<Tooltip
anchorId="import-paths-tooltip"
@@ -238,7 +238,7 @@ const ProtobufSettings = ({ collection }) => {
<div>
{importPaths.some((path) => !path.exists) && (
<div className="text-xs text-red-600 dark:text-red-400 mb-2 flex items-center p-2 rounded" data-testid="protobuf-invalid-import-paths-message">
<div className="error-message text-xs mb-2 flex items-center p-2" data-testid="protobuf-invalid-import-paths-message">
<IconAlertCircle size={14} className="mr-1" />
Some import paths cannot be found at their specified locations.
</div>
@@ -247,15 +247,15 @@ const ProtobufSettings = ({ collection }) => {
<table className="w-full border-collapse" data-testid="protobuf-import-paths-table">
<thead>
<tr>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th>
</th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th>
Directory
</th>
<th className="text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th>
Path
</th>
<th className="text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider border border-gray-200 dark:border-gray-700 px-3 py-2">
<th className="text-right">
Actions
</th>
</tr>
@@ -263,10 +263,10 @@ const ProtobufSettings = ({ collection }) => {
<tbody>
{importPaths.length === 0 ? (
<tr>
<td colSpan="4" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
<div className="flex flex-col items-center">
<IconFolder size={24} className="text-gray-400 mb-2" />
<span className="text-gray-500 dark:text-gray-400">No import paths added</span>
<td colSpan="4" className="text-center">
<div className="empty-state flex flex-col items-center">
<IconFolder size={24} className="empty-icon mb-2" />
<span className="empty-text">No import paths added</span>
</div>
</td>
</tr>
@@ -276,37 +276,37 @@ const ProtobufSettings = ({ collection }) => {
return (
<tr key={index}>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<td>
<input
type="checkbox"
checked={importPath.enabled}
onChange={() => handleToggleImportPath(index)}
className="h-4 w-4 text-gray-600 focus:ring-gray-500 border-gray-300 dark:border-gray-600 rounded"
className="h-4 w-4"
title={importPath.enabled ? 'Disable this import path' : 'Enable this import path'}
data-testid="protobuf-import-path-checkbox"
/>
</td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<td>
<div className="flex items-center">
<IconFolder size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
<span className="font-medium text-gray-900 dark:text-gray-100">
<IconFolder size={16} className="folder-icon mr-2" />
<span className="directory-name">
{getBasename(collection.pathname, importPath.path)}
</span>
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
{!isValid && <IconAlertCircle size={12} className="invalid-indicator ml-2" />}
</div>
</td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono">
<td>
<div className="path-text">
{importPath.path}
</div>
</td>
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2 text-right">
<td className="text-right">
<div className="flex items-center justify-end space-x-1">
{!isValid && (
<button
type="button"
onClick={() => handleReplaceImportPath(index)}
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 p-1 rounded"
className="action-button replace-button"
title="Replace directory"
>
<IconFileImport size={14} />
@@ -315,7 +315,7 @@ const ProtobufSettings = ({ collection }) => {
<button
type="button"
onClick={() => handleRemoveImportPath(index)}
className="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 p-1 rounded"
className="action-button remove-button"
title="Remove import path"
data-testid="protobuf-remove-import-path-button"
>

View File

@@ -14,11 +14,11 @@ const StyledWrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
outline: none !important;
}
}

View File

@@ -12,7 +12,19 @@ import Button from 'ui/Button';
const ProxySettings = ({ collection }) => {
const dispatch = useDispatch();
const initialProxyConfig = { enabled: 'global', protocol: 'http', hostname: '', port: '', auth: { enabled: false, username: '', password: '' }, bypassProxy: '' };
const initialProxyConfig = {
inherit: true,
config: {
protocol: 'http',
hostname: '',
port: '',
auth: {
username: '',
password: ''
},
bypassProxy: ''
}
};
// Get proxy from draft.brunoConfig if it exists, otherwise from brunoConfig
const currentProxyConfig = collection.draft?.brunoConfig
@@ -82,34 +94,57 @@ const ProxySettings = ({ collection }) => {
const handleEnabledChange = (e) => {
const value = e.target.value;
// Convert string to boolean or keep as 'global'
const enabled = value === 'true' ? true : value === 'false' ? false : 'global';
updateProxy({ enabled });
// Map UI values to new format
if (value === 'inherit') {
updateProxy({ disabled: false, inherit: true });
} else if (value === 'true') {
updateProxy({ disabled: false, inherit: false });
} else {
updateProxy({ disabled: true, inherit: false });
}
};
const handleProtocolChange = (e) => {
updateProxy({ protocol: e.target.value });
updateProxy({
config: {
...currentProxyConfig.config,
protocol: e.target.value
}
});
};
const handleHostnameChange = (e) => {
const hostname = e.target.value;
if (validateHostnameOnChange(hostname)) {
updateProxy({ hostname });
updateProxy({
config: {
...currentProxyConfig.config,
hostname
}
});
}
};
const handlePortChange = (e) => {
const port = e.target.value ? Number(e.target.value) : '';
if (validatePortOnChange(port)) {
updateProxy({ port });
updateProxy({
config: {
...currentProxyConfig.config,
port
}
});
}
};
const handleAuthEnabledChange = (e) => {
updateProxy({
auth: {
...currentProxyConfig.auth,
enabled: e.target.checked
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
disabled: !e.target.checked
}
}
});
};
@@ -118,9 +153,12 @@ const ProxySettings = ({ collection }) => {
const username = e.target.value;
if (validateAuthUsernameOnChange(username)) {
updateProxy({
auth: {
...currentProxyConfig.auth,
username
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
username
}
}
});
}
@@ -130,9 +168,12 @@ const ProxySettings = ({ collection }) => {
const password = e.target.value;
if (validateAuthPasswordOnChange(password)) {
updateProxy({
auth: {
...currentProxyConfig.auth,
password
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
password
}
}
});
}
@@ -141,11 +182,19 @@ const ProxySettings = ({ collection }) => {
const handleBypassProxyChange = (e) => {
const bypassProxy = e.target.value;
if (validateBypassProxyOnChange(bypassProxy)) {
updateProxy({ bypassProxy });
updateProxy({
config: {
...currentProxyConfig.config,
bypassProxy
}
});
}
};
const enabledValue = currentProxyConfig.enabled === true ? 'true' : currentProxyConfig.enabled === false ? 'false' : 'global';
// Map new format to UI values
const disabled = currentProxyConfig.disabled || false;
const inherit = currentProxyConfig.inherit !== undefined ? currentProxyConfig.inherit : true;
const enabledValue = disabled ? 'false' : (inherit ? 'inherit' : 'true');
return (
<StyledWrapper className="h-full w-full">
@@ -157,9 +206,9 @@ const ProxySettings = ({ collection }) => {
<InfoTip infotipId="request-var">
<div>
<ul>
<li><span style={{ width: '50px', display: 'inline-block' }}>global</span> - use global proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>enabled</span> - use collection proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>disable</span> - disable proxy</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>inherit</span> - inherit from global preferences</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>enabled</span> - use collection-specific proxy config</li>
<li><span style={{ width: '50px', display: 'inline-block' }}>disabled</span> - disable proxy for this collection</li>
</ul>
</div>
</InfoTip>
@@ -169,12 +218,12 @@ const ProxySettings = ({ collection }) => {
<input
type="radio"
name="enabled"
value="global"
checked={enabledValue === 'global'}
value="inherit"
checked={enabledValue === 'inherit'}
onChange={handleEnabledChange}
className="mr-1"
/>
global
inherit
</label>
<label className="flex items-center ml-4">
<input
@@ -200,164 +249,168 @@ const ProxySettings = ({ collection }) => {
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
{enabledValue === 'true' && (
<>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={(currentProxyConfig.config?.protocol || 'http') === 'http'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={(currentProxyConfig.config?.protocol || 'http') === 'https'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={(currentProxyConfig.config?.protocol || 'http') === 'socks4'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={(currentProxyConfig.config?.protocol || 'http') === 'socks5'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
type="radio"
name="protocol"
value="http"
checked={(currentProxyConfig.protocol || 'http') === 'http'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={(currentProxyConfig.protocol || 'http') === 'https'}
onChange={handleProtocolChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={(currentProxyConfig.protocol || 'http') === 'socks4'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={(currentProxyConfig.protocol || 'http') === 'socks5'}
onChange={handleProtocolChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleHostnameChange}
value={currentProxyConfig.hostname || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handlePortChange}
value={currentProxyConfig.port || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={currentProxyConfig.auth?.enabled || false}
onChange={handleAuthEnabledChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.auth?.username || ''}
onChange={handleAuthUsernameChange}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.auth?.password || ''}
onChange={handleAuthPasswordChange}
onChange={handleHostnameChange}
value={currentProxyConfig.config?.hostname || ''}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
</div>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleBypassProxyChange}
value={currentProxyConfig.bypassProxy || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handlePortChange}
value={currentProxyConfig.config?.port || ''}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.disabled">
Auth
</label>
<input
type="checkbox"
name="auth.disabled"
checked={!currentProxyConfig.config?.auth?.disabled}
onChange={handleAuthEnabledChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.config?.auth?.username || ''}
onChange={handleAuthUsernameChange}
/>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[1.70rem] relative">
<input
id="auth.password"
type={passwordVisible ? 'text' : 'password'}
name="auth.password"
className="outline-none bg-transparent w-[10.5rem]"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={currentProxyConfig.config?.auth?.password || ''}
onChange={handleAuthPasswordChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
</div>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleBypassProxyChange}
value={currentProxyConfig.config?.bypassProxy || ''}
/>
</div>
</>
)}
<div className="mt-6">
<Button type="submit" size="sm" onClick={handleSave}>
Save

View File

@@ -8,7 +8,7 @@ const StyledWrapper = styled.div`
}
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -6,20 +6,38 @@ import { updateCollectionRequestScript, updateCollectionResponseScript } from 'p
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import { Tabs, TabsList, TabsTrigger, TabsContent } from 'components/Tabs';
import StatusDot from 'components/StatusDot';
import StyledWrapper from './StyledWrapper';
import Button from 'ui/Button';
const Script = ({ collection }) => {
const dispatch = useDispatch();
const [activeTab, setActiveTab] = useState('pre-request');
const preRequestEditorRef = useRef(null);
const postResponseEditorRef = useRef(null);
const requestScript = collection.draft?.root ? get(collection, 'draft.root.request.script.req', '') : get(collection, 'root.request.script.req', '');
const responseScript = collection.draft?.root ? get(collection, 'draft.root.request.script.res', '') : get(collection, 'root.request.script.res', '');
// Default to post-response if pre-request script is empty
const getInitialTab = () => {
const hasPreRequestScript = requestScript && requestScript.trim().length > 0;
return hasPreRequestScript ? 'pre-request' : 'post-response';
};
const [activeTab, setActiveTab] = useState(getInitialTab);
const prevCollectionUidRef = useRef(collection.uid);
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
// Update active tab only when switching to a different collection
useEffect(() => {
if (prevCollectionUidRef.current !== collection.uid) {
prevCollectionUidRef.current = collection.uid;
const hasPreRequestScript = requestScript && requestScript.trim().length > 0;
setActiveTab(hasPreRequestScript ? 'pre-request' : 'post-response');
}
}, [collection.uid, requestScript]);
// Refresh CodeMirror when tab becomes visible
useEffect(() => {
const timer = setTimeout(() => {
@@ -56,15 +74,21 @@ const Script = ({ collection }) => {
};
return (
<StyledWrapper className="w-full flex flex-col h-full pt-4">
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">
Write pre and post-request scripts that will run before and after any request in this collection is sent.
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="pre-request">Pre Request</TabsTrigger>
<TabsTrigger value="post-response">Post Response</TabsTrigger>
<TabsTrigger value="pre-request">
Pre Request
{requestScript && requestScript.trim().length > 0 && <StatusDot />}
</TabsTrigger>
<TabsTrigger value="post-response">
Post Response
{responseScript && responseScript.trim().length > 0 && <StatusDot />}
</TabsTrigger>
</TabsList>
<TabsContent value="pre-request" className="mt-2">

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,
@@ -19,6 +19,10 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&:hover {
color: ${(props) => props.theme.tabs.active.color} !important;
}
&.active {
font-weight: ${(props) => props.theme.tabs.active.fontWeight} !important;
color: ${(props) => props.theme.tabs.active.color} !important;
@@ -40,6 +44,11 @@ const StyledWrapper = styled.div`
.muted {
color: ${(props) => props.theme.colors.text.muted};
}
input[type='radio'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
`;
export default StyledWrapper;

View File

@@ -4,7 +4,7 @@ const StyledWrapper = styled.div`
max-width: 800px;
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -46,14 +46,14 @@ const VarsTable = ({ collection, vars, varType }) => {
</div>
),
placeholder: varType === 'request' ? 'Value' : 'Expr',
render: ({ row, value, onChange, isLastEmptyRow }) => (
render: ({ value, onChange }) => (
<MultiLineEditor
value={value || ''}
theme={storedTheme}
onSave={onSave}
onChange={onChange}
collection={collection}
placeholder={isLastEmptyRow ? (varType === 'request' ? 'Value' : 'Expr') : ''}
placeholder={!value ? (varType === 'request' ? 'Value' : 'Expr') : ''}
/>
)
}

View File

@@ -14,7 +14,7 @@ const Vars = ({ collection }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="flex-1">
<div className="mb-3 title text-xs">Pre Request</div>
<VarsTable collection={collection} vars={requestVars} varType="request" />
</div>

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { useTheme } from 'providers/Theme';
const ColorBadge = ({ color, size = 10 }) => {
const sizeValue = typeof size === 'string' ? size : `${size}px`;
const { theme } = useTheme();
return (
<div
className="flex-shrink-0 rounded-full"
style={{
width: sizeValue,
height: sizeValue,
backgroundColor: color || 'transparent'
}}
/>
);
};
export default ColorBadge;

View File

@@ -0,0 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
`;
export default StyledWrapper;

View File

@@ -0,0 +1,164 @@
import React, { useState, useEffect, useRef } from 'react';
import { IconBan, IconBrush } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import ColorBadge from 'components/ColorBadge';
import StyledWrapper from './StyledWrapper';
import { parseToRgb, toColorString } from 'polished';
import ColorRangePicker from 'components/ColorRange/index';
const PRESET_COLORS = [
'#CE4F3B',
'#2E8A54',
'#346AB2',
'#C77A0F',
'#B83D7F',
'#8D44B2'
];
const COLOR_RANGE_SEQUENCE = ['#D85D43', '#F4BB74', '#61DCB1', '#7EBDF2', '#D48ADE', '#B491E5'];
/**
* @param {string} hex
* @returns {red:string,green:string,blue:string}
*/
const hexToRgb = (hex) => {
try {
return parseToRgb(hex);
} catch (err) {
return { red: 0, green: 0, blue: 0 };
}
};
const rgbToHex = (r, g, b) => {
return toColorString({ red: Math.round(r), green: Math.round(g), blue: Math.round(b) });
};
const interpolateColor = (position) => {
const numColors = COLOR_RANGE_SEQUENCE.length;
const scaledPos = (position / 100) * (numColors - 1);
const index = Math.floor(scaledPos);
const fraction = scaledPos - index;
if (index >= numColors - 1) {
return COLOR_RANGE_SEQUENCE[numColors - 1];
}
const color1 = hexToRgb(COLOR_RANGE_SEQUENCE[index]);
const color2 = hexToRgb(COLOR_RANGE_SEQUENCE[index + 1]);
const r = color1.red + (color2.red - color1.red) * fraction;
const g = color1.green + (color2.green - color1.green) * fraction;
const b = color1.blue + (color2.blue - color1.blue) * fraction;
return rgbToHex(r, g, b);
};
const findClosestPosition = (hex) => {
if (!hex) return 0;
const target = hexToRgb(hex);
let closestPos = 0;
let minDistance = Infinity;
for (let pos = 0; pos <= 100; pos++) {
const color = hexToRgb(interpolateColor(pos));
const distance = Math.sqrt(
Math.pow(target.red - color.red, 2) + Math.pow(target.green - color.green, 2) + Math.pow(target.blue - color.blue, 2)
);
if (distance < minDistance) {
minDistance = distance;
closestPos = pos;
}
}
return closestPos;
};
const ColorPickerIcon = ({ color }) => {
if (color) {
return <ColorBadge color={color} size={8} />;
}
return <IconBrush size={14} strokeWidth={1.5} className="opacity-70" />;
};
const ColorPicker = ({ color, onChange, icon }) => {
const [sliderPosition, setSliderPosition] = useState(() =>
color && !PRESET_COLORS.includes(color) ? findClosestPosition(color) : 0
);
const [customColor, setCustomColor] = useState(() =>
color && !PRESET_COLORS.includes(color) ? color : COLOR_RANGE_SEQUENCE[0]
);
const pendingColorRef = useRef(customColor);
const handleColorSelect = (selectedColor) => {
onChange(selectedColor);
};
const handleSliderChange = (e) => {
const newPosition = parseInt(e.target.value, 10);
setSliderPosition(newPosition);
const newColor = interpolateColor(newPosition);
setCustomColor(newColor);
pendingColorRef.current = newColor;
};
const handleSliderEnd = () => {
onChange(pendingColorRef.current);
};
const defaultIcon = (
<div className="cursor-pointer flex items-center" title="Change color">
<ColorPickerIcon color={color} />
</div>
);
const colorPickerContent = (
<StyledWrapper>
<div className="p-2">
<div className="flex flex-wrap gap-1.5 justify-between items-center">
<div
className="w-5 h-5 cursor-pointer flex items-center justify-center transition-transform duration-100 hover:scale-110"
onClick={() => handleColorSelect(null)}
title="No color"
>
<IconBan size={20} strokeWidth={1.5} />
</div>
{PRESET_COLORS.map((presetColor, index) => (
<div
key={index}
className={`w-5 h-5 rounded cursor-pointer flex items-center justify-center transition-transform duration-100 hover:scale-110 border-2 border-transparent
${color === presetColor ? 'border-solid !border-current' : ''}
`}
style={{ backgroundColor: presetColor }}
onClick={() => handleColorSelect(presetColor)}
title={presetColor}
/>
))}
</div>
<div className="flex items-center gap-2 mt-2 pt-0.5">
<div
className="w-5 h-5 rounded-full flex-shrink-0 cursor-pointer"
style={{ backgroundColor: customColor }}
onClick={() => handleColorSelect(customColor)}
title="Custom color"
/>
<ColorRangePicker
className="flex-1 flex"
value={sliderPosition}
onChange={handleSliderChange}
onMouseUp={handleSliderEnd}
selectedColor={customColor}
colorRange={COLOR_RANGE_SEQUENCE}
/>
</div>
</div>
</StyledWrapper>
);
return (
<Dropdown icon={icon || defaultIcon} placement="bottom-start">
{colorPickerContent}
</Dropdown>
);
};
export default ColorPicker;

View File

@@ -0,0 +1,46 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.hue-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
outline: none;
}
.hue-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: ${(props) => props.color ?? props.theme.bg};
border: none;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: transform 0.1s ease;
}
.hue-slider::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
.hue-slider::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: ${(props) => props.color ?? props.theme.bg};
border: none;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: transform 0.1s ease;
}
.hue-slider::-moz-range-thumb:hover {
transform: scale(1.1);
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,23 @@
import StyledWrapper from './StyledWrapper';
const ColorRangePicker = ({ selectedColor, className, value, onChange, colorRange, ...props }) => {
return (
<StyledWrapper color={selectedColor} className={className}>
<input
type="range"
min="0"
max="100"
value={value}
onChange={onChange}
className="hue-slider"
style={{
background: `linear-gradient(to right, ${colorRange.join(',')})`
}}
title="Adjust color"
{...props}
/>
</StyledWrapper>
);
};
export default ColorRangePicker;

View File

@@ -1,5 +1,26 @@
import styled from 'styled-components';
const StyledWrapper = styled.div``;
const StyledWrapper = styled.div`
/* Info icon */
.info-icon {
color: ${(props) => props.theme.colors.text.muted};
}
/* Required field asterisk */
.required-asterisk {
color: ${(props) => props.theme.colors.text.danger};
}
/* Error messages */
.error-message {
color: ${(props) => props.theme.colors.text.danger};
}
/* Checkbox */
input[type='checkbox'] {
cursor: pointer;
accent-color: ${(props) => props.theme.primary.solid};
}
`;
export default StyledWrapper;

View File

@@ -11,6 +11,7 @@ import moment from 'moment';
import 'moment-timezone';
import { Tooltip } from 'react-tooltip';
import { isEmpty } from 'lodash';
import StyledWrapper from './StyledWrapper';
const removeEmptyValues = (obj) => {
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== null && value !== undefined));
@@ -225,145 +226,147 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
</div>
)}
>
<form onSubmit={(e) => e.preventDefault()} className="px-2">
{isRawMode ? (
<div>
<div className="flex items-center gap-2 mb-1">
<label className="block">Set-Cookie String</label>
<IconInfoCircle id="cookie-raw-info" size={16} strokeWidth={1.5} className="text-gray-400" />
<Tooltip
anchorId="cookie-raw-info"
className="tooltip-mod"
html="Key, Path, and Domain are immutable properties and cannot be modified for existing cookies"
<StyledWrapper>
<form onSubmit={(e) => e.preventDefault()} className="px-2">
{isRawMode ? (
<div>
<div className="flex items-center gap-2 mb-1">
<label className="block">Set-Cookie String</label>
<IconInfoCircle id="cookie-raw-info" size={16} strokeWidth={1.5} className="info-icon" />
<Tooltip
anchorId="cookie-raw-info"
className="tooltip-mod"
html="Key, Path, and Domain are immutable properties and cannot be modified for existing cookies"
/>
</div>
<textarea
value={cookieString}
onChange={(e) => setCookieString(e.target.value)}
className="block textbox w-full h-24"
placeholder="key=value; key2=value2"
/>
</div>
<textarea
value={cookieString}
onChange={(e) => setCookieString(e.target.value)}
className="block textbox w-full h-24"
placeholder="key=value; key2=value2"
/>
</div>
) : (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block mb-1">
Domain<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="domain"
// Auto-focus if its add-new i.e. when domain prop is empty
autoFocus={!domain && !formik.values.domain}
value={formik.values.domain}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.domain && formik.errors.domain && (
<div className="text-red-500 mt-1">{formik.errors.domain}</div>
)}
</div>
<div>
<label className="block mb-1">Path</label>
<input
type="text"
name="path"
value={formik.values.path}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.path && formik.errors.path && (
<div className="text-red-500 mt-1">{formik.errors.path}</div>
)}
</div>
<div>
<label className="block mb-1">
Key<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="key"
// Auto focus when add-for-domain i.e. if domain is already prefilled
autoFocus={!!domain && !formik.values.key}
value={formik.values.key}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.key && formik.errors.key && (
<div className="text-red-500 mt-1">{formik.errors.key}</div>
)}
) : (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block mb-1">
Domain<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="domain"
// Auto-focus if its add-new i.e. when domain prop is empty
autoFocus={!domain && !formik.values.domain}
value={formik.values.domain}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.domain && formik.errors.domain && (
<div className="error-message mt-1">{formik.errors.domain}</div>
)}
</div>
<div>
<label className="block mb-1">Path</label>
<input
type="text"
name="path"
value={formik.values.path}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.path && formik.errors.path && (
<div className="error-message mt-1">{formik.errors.path}</div>
)}
</div>
<div>
<label className="block mb-1">
Key<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="key"
// Auto focus when add-for-domain i.e. if domain is already prefilled
autoFocus={!!domain && !formik.values.key}
value={formik.values.key}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full disabled:opacity-50"
disabled={!!cookie}
/>
{formik.touched.key && formik.errors.key && (
<div className="error-message mt-1">{formik.errors.key}</div>
)}
</div>
<div>
<label className="block mb-1">
Value<span className="required-asterisk">*</span>{' '}
</label>
<input
type="text"
name="value"
// Auto-focus when its in edit mode i.e. cookie prop is present
autoFocus={!!cookie}
value={formik.values.value}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full"
/>
{formik.touched.value && formik.errors.value && (
<div className="error-message mt-1">{formik.errors.value}</div>
)}
</div>
</div>
<div>
<label className="block mb-1">
Value<span className="text-red-600">*</span>{' '}
</label>
<input
type="text"
name="value"
// Auto-focus when its in edit mode i.e. cookie prop is present
autoFocus={!!cookie}
value={formik.values.value}
onChange={formik.handleChange}
className="block textbox non-passphrase-input w-full"
/>
{formik.touched.value && formik.errors.value && (
<div className="text-red-500 mt-1">{formik.errors.value}</div>
)}
{/* Date Picker */}
<div className="w-full flex items-end">
<div>
<label className="block mb-1">Expiration ({moment.tz.guess()})</label>
<input
type="datetime-local"
name="expires"
value={formik.values.expires}
onChange={(e) => {
formik.handleChange(e);
}}
className="block textbox non-passphrase-input w-full"
min={moment().format(moment.HTML5_FMT.DATETIME_LOCAL)}
/>
{formik.touched.expires && formik.errors.expires && (
<div className="error-message mt-1">{formik.errors.expires}</div>
)}
</div>
{/* Checkboxes */}
<div className="flex space-x-4 ml-auto">
<label className="flex items-center">
<input
type="checkbox"
name="secure"
checked={formik.values.secure}
onChange={formik.handleChange}
className="mr-2"
/>
<span>Secure</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
name="httpOnly"
checked={formik.values.httpOnly}
onChange={formik.handleChange}
className="mr-2"
/>
<span>HTTP Only</span>
</label>
</div>
</div>
</div>
{/* Date Picker */}
<div className="w-full flex items-end">
<div>
<label className="block mb-1">Expiration ({moment.tz.guess()})</label>
<input
type="datetime-local"
name="expires"
value={formik.values.expires}
onChange={(e) => {
formik.handleChange(e);
}}
className="block textbox non-passphrase-input w-full"
min={moment().format(moment.HTML5_FMT.DATETIME_LOCAL)}
/>
{formik.touched.expires && formik.errors.expires && (
<div className="text-red-500 mt-1">{formik.errors.expires}</div>
)}
</div>
{/* Checkboxes */}
<div className="flex space-x-4 ml-auto">
<label className="flex items-center">
<input
type="checkbox"
name="secure"
checked={formik.values.secure}
onChange={formik.handleChange}
className="mr-2"
/>
<span>Secure</span>
</label>
<label className="flex items-center">
<input
type="checkbox"
name="httpOnly"
checked={formik.values.httpOnly}
onChange={formik.handleChange}
className="mr-2"
/>
<span>HTTP Only</span>
</label>
</div>
</div>
</div>
)}
</form>
)}
</form>
</StyledWrapper>
</Modal>
);
};

View File

@@ -28,11 +28,11 @@ const Wrapper = styled.div`
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
background-color: ${(props) => props.theme.input.bg};
border: 1px solid ${(props) => props.theme.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
outline: none !important;
}
}
@@ -70,6 +70,92 @@ const Wrapper = styled.div`
background-size: 100% 30px, 100% 30px, 100% 10px, 100% 10px;
background-attachment: local, local, scroll, scroll;
}
/* Warning icon */
.warning-icon {
color: ${(props) => props.theme.colors.text.warning};
}
/* Empty state */
.empty-icon {
color: ${(props) => props.theme.colors.text.muted};
}
.empty-text {
color: ${(props) => props.theme.colors.text.muted};
}
/* Domain count text */
.domain-count {
color: ${(props) => props.theme.colors.text.muted};
}
/* Action buttons */
.action-button {
color: ${(props) => props.theme.colors.text.muted};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.text};
}
}
.action-button-danger {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
/* Table styles */
table {
thead {
tr {
border-bottom: 1px solid ${(props) => props.theme.table.border};
color: ${(props) => props.theme.table.thead.color};
th {
color: ${(props) => props.theme.table.thead.color};
}
}
}
tbody {
tr {
border-bottom: 1px solid ${(props) => props.theme.table.border};
&:last-child {
border-bottom: none;
}
}
}
}
/* Edit button */
.edit-button {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.muted};
}
}
/* Delete button */
.delete-button {
color: ${(props) => props.theme.text};
transition: color 0.2s;
cursor: pointer;
&:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
`;
export default Wrapper;

View File

@@ -14,7 +14,7 @@ import Button from 'ui/Button';
const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
<Modal onClose={onClose} handleCancel={onClose} title="Clear Domain Cookies" hideFooter={true}>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<IconAlertTriangle size={32} strokeWidth={1.5} className="warning-icon" />
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
</div>
<div className="font-normal mt-4">
@@ -23,12 +23,12 @@ const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
<div className="flex justify-between mt-6">
<div>
<Button size="sm" color="secondary" variant="ghost" onClick={onClose}>
<Button color="secondary" variant="ghost" onClick={onClose}>
Close
</Button>
</div>
<div>
<Button size="sm" color="danger" onClick={onClear}>
<Button color="danger" onClick={onClear}>
Clear All
</Button>
</div>
@@ -39,7 +39,7 @@ const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
<Modal onClose={onClose} handleCancel={onClose} title="Delete Cookie" hideFooter={true}>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<IconAlertTriangle size={32} strokeWidth={1.5} className="warning-icon" />
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
</div>
<div className="font-normal mt-4">
@@ -48,12 +48,12 @@ const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
<div className="flex justify-between mt-6">
<div>
<Button size="sm" color="secondary" variant="ghost" onClick={onClose}>
<Button color="secondary" variant="ghost" onClick={onClose}>
Close
</Button>
</div>
<div>
<Button size="sm" color="danger" onClick={onDelete}>
<Button color="danger" onClick={onDelete}>
Delete
</Button>
</div>
@@ -159,27 +159,28 @@ const CollectionProperties = ({ onClose }) => {
{!cookies || !cookies.length ? (
// No cookies found
<div className="flex items-center justify-center flex-col">
<IconCookieOff size={48} strokeWidth={1.5} className="text-gray-500" />
<IconCookieOff size={48} strokeWidth={1.5} className="empty-icon" />
<h2 className="text-lg font-medium mt-4">No cookies found</h2>
<p className="text-gray-500 mt-2">Add cookies to get started</p>
<button
<p className="empty-text mt-2">Add cookies to get started</p>
<Button
type="submit"
className="submit btn btn-sm btn-secondary flex items-center gap-1 mt-8"
size="sm"
className="mt-8"
icon={<IconCirclePlus strokeWidth={1.5} size={16} />}
onClick={(e) => {
e.stopPropagation();
handleAddCookie();
}}
>
<IconCirclePlus strokeWidth={1.5} size={16} />
<span>Add Cookie</span>
</button>
Add Cookie
</Button>
</div>
) : cookies.length && !filteredCookies.length ? (
// No search results
<div className="flex items-center justify-center flex-col">
<IconSearch size={48} />
<h2 className="text-lg font-medium mt-4">No search results</h2>
<p className="text-gray-500 mt-2">Try a different search term</p>
<p className="empty-text mt-2">Try a different search term</p>
</div>
) : (
// Show cookies list
@@ -190,14 +191,14 @@ const CollectionProperties = ({ onClose }) => {
<Accordion.Header index={i} className="flex items-center">
<div className="flex items-center">
<span>{domainWithCookies.domain}</span>
<span className="ml-2 text-xs dark:text-gray-300 text-gray-500">
<span className="domain-count ml-2 text-xs">
({domainWithCookies.cookies.length}{' '}
{domainWithCookies.cookies.length === 1 ? 'cookie' : 'cookies'})
</span>
<div className="ml-auto flex items-center gap-2">
<button
type="submit"
className="flex items-center gap-1 text-gray-500 hover:text-gray-950 dark:text-white dark:hover:text-gray-300"
className="action-button flex items-center gap-1"
onClick={(e) => {
e.stopPropagation();
handleAddCookie(domainWithCookies.domain);
@@ -210,7 +211,7 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleClearDomainCookies(domainWithCookies.domain);
}}
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600 mr-2"
className="action-button-danger mr-2"
>
<IconTrash strokeWidth={1.5} size={16} />
</button>
@@ -221,7 +222,7 @@ const CollectionProperties = ({ onClose }) => {
<div className="flex items-center justify-between">
<table className="w-full">
<thead>
<tr className="text-left border-b border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-gray-300">
<tr className="text-left">
<th className="py-2 px-4 font-medium w-32">Name</th>
<th className="py-2 px-4 font-medium w-52">Value</th>
<th className="py-2 px-4 font-medium">Path</th>
@@ -233,7 +234,7 @@ const CollectionProperties = ({ onClose }) => {
</thead>
<tbody>
{domainWithCookies.cookies.map((cookie) => (
<tr key={cookie.key} className="border-b border-gray-200 dark:border-neutral-600 last:border-none">
<tr key={cookie.key}>
<td className="py-2 px-4 truncate">
<span id={`cookie-key-${cookie.key}`}>{cookie.key}</span>
<Tooltip
@@ -274,8 +275,7 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleEditCookie(domainWithCookies.domain, cookie);
}}
className="text-gray-700 hover:text-gray-950
dark:text-white dark:hover:text-gray-300"
className="edit-button"
>
<IconEdit strokeWidth={1.5} size={16} />
</button>
@@ -284,7 +284,7 @@ const CollectionProperties = ({ onClose }) => {
e.stopPropagation();
handleDeleteCookie(domainWithCookies.domain, cookie.path, cookie.key);
}}
className="text-gray-950 dark:text-white dark:hover:hover:text-red-600 hover:text-red-600"
className="delete-button"
>
<IconTrash strokeWidth={1.5} size={16} />
</button>

View File

@@ -0,0 +1,246 @@
import React, { useState, useRef, useCallback, useMemo } from 'react';
import { IconPlus, IconApi, IconBrandGraphql, IconPlugConnected, IconCode } from '@tabler/icons';
import ActionIcon from 'ui/ActionIcon/index';
import Dropdown from 'components/Dropdown';
import { newHttpRequest, newGrpcRequest, newWsRequest } from 'providers/ReduxStore/slices/collections/actions';
import { sanitizeName } from 'utils/common/regex';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
import { flattenItems, isItemARequest, isItemTransientRequest } from 'utils/collections';
import filter from 'lodash/filter';
import { get } from 'lodash';
import { formatIpcError } from 'utils/common/error';
const REQUEST_TYPE = {
HTTP: 'http',
GRAPHQL: 'graphql',
GRPC: 'grpc',
WEBSOCKET: 'websocket'
};
/**
* Generate a request name for transient requests in the pattern "Untitled {Count}"
* @param {Object} collection - The collection object
* @returns {string} A request name like "Untitled 1", "Untitled 2", etc.
*/
const generateTransientRequestName = (collection) => {
if (!collection || !collection.items) {
return 'Untitled 1';
}
const allItems = flattenItems(collection.items);
const transientRequests = filter(allItems, (item) => {
return isItemTransientRequest(item);
});
// Find the highest "Untitled X" number among transient requests
let maxNumber = 0;
transientRequests.forEach((item) => {
const match = item.name?.match(/^Untitled (\d+)$/);
if (match) {
const number = parseInt(match[1], 10);
if (number > maxNumber) {
maxNumber = number;
}
}
});
// Increment from the highest number found, or start at 1 if none found
const count = maxNumber + 1;
return `Untitled ${count}`;
};
const CreateTransientRequest = ({ collectionUid }) => {
const [dropdownVisible, setDropdownVisible] = useState(false);
const dropdownTippyRef = useRef();
const dispatch = useDispatch();
const collections = useSelector((state) => state.collections.collections);
const collection = useMemo(() => {
return collections?.find((c) => c.uid === collectionUid);
}, [collections, collectionUid]);
const collectionPresets = useMemo(() => {
return get(collection, collection?.draft?.brunoConfig ? 'draft.brunoConfig.presets' : 'brunoConfig.presets', {
requestType: 'http',
requestUrl: ''
});
}, [collection]);
const onDropdownCreate = (ref) => {
dropdownTippyRef.current = ref;
if (ref) {
ref.setProps({
onHide: () => {
setDropdownVisible(false);
}
});
}
};
const handleLeftClick = () => {
handleItemClick(collectionPresets.requestType);
};
const handleRightClick = (e) => {
e.preventDefault();
setDropdownVisible(true);
};
const handleCreateHttpRequest = useCallback(() => {
if (!collection) return;
const uniqueName = generateTransientRequestName(collection);
const filename = sanitizeName(uniqueName);
dispatch(
newHttpRequest({
requestName: uniqueName,
filename: filename,
requestType: 'http-request',
requestUrl: collectionPresets.requestUrl,
requestMethod: 'GET',
collectionUid: collection.uid,
itemUid: null,
isTransient: true
})
).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request'));
}, [dispatch, collection, collectionPresets.requestUrl]);
const handleCreateGraphQLRequest = useCallback(() => {
if (!collection) return;
const uniqueName = generateTransientRequestName(collection);
const filename = sanitizeName(uniqueName);
dispatch(
newHttpRequest({
requestName: uniqueName,
filename: filename,
requestType: 'graphql-request',
requestUrl: collectionPresets.requestUrl,
requestMethod: 'POST',
collectionUid: collection.uid,
itemUid: null,
isTransient: true,
body: {
mode: 'graphql',
graphql: {
query: '',
variables: ''
}
}
})
).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request'));
}, [dispatch, collection, collectionPresets.requestUrl]);
const handleCreateWebSocketRequest = useCallback(() => {
if (!collection) return;
const uniqueName = generateTransientRequestName(collection);
const filename = sanitizeName(uniqueName);
dispatch(
newWsRequest({
requestName: uniqueName,
filename: filename,
requestUrl: collectionPresets.requestUrl,
requestMethod: 'ws',
collectionUid: collection.uid,
itemUid: null,
isTransient: true
})
).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request'));
}, [dispatch, collection, collectionPresets.requestUrl]);
const handleCreateGrpcRequest = useCallback(() => {
if (!collection) return;
const uniqueName = generateTransientRequestName(collection);
const filename = sanitizeName(uniqueName);
dispatch(
newGrpcRequest({
requestName: uniqueName,
filename: filename,
requestUrl: collectionPresets.requestUrl,
collectionUid: collection.uid,
itemUid: null,
isTransient: true
})
).catch((err) => toast.error(formatIpcError(err) || 'An error occurred while adding the request'));
}, [dispatch, collection, collectionPresets.requestUrl]);
const handleItemClick = (type) => {
if (dropdownTippyRef.current) {
dropdownTippyRef.current.hide();
}
switch (type) {
case REQUEST_TYPE.HTTP:
handleCreateHttpRequest();
break;
case REQUEST_TYPE.GRAPHQL:
handleCreateGraphQLRequest();
break;
case REQUEST_TYPE.GRPC:
handleCreateGrpcRequest();
break;
case REQUEST_TYPE.WEBSOCKET:
handleCreateWebSocketRequest();
break;
}
};
if (!collection) {
return null;
}
const IconButton = (
<ActionIcon
onClick={handleLeftClick}
onContextMenu={handleRightClick}
aria-label="New Transient Request"
size="lg"
style={{ marginBottom: '3px' }}
>
<IconPlus size={18} strokeWidth={1.5} />
</ActionIcon>
);
return (
<Dropdown
icon={IconButton}
visible={dropdownVisible}
onCreate={onDropdownCreate}
onClickOutside={() => setDropdownVisible(false)}
placement="bottom-end"
>
<div className="dropdown-item" onClick={() => handleItemClick(REQUEST_TYPE.HTTP)}>
<div className="dropdown-icon">
<IconApi size={16} strokeWidth={2} />
</div>
<div className="dropdown-label">HTTP</div>
</div>
<div className="dropdown-item" onClick={() => handleItemClick(REQUEST_TYPE.GRAPHQL)}>
<div className="dropdown-icon">
<IconBrandGraphql size={16} strokeWidth={2} />
</div>
<div className="dropdown-label">GraphQL</div>
</div>
<div className="dropdown-item" onClick={() => handleItemClick(REQUEST_TYPE.GRPC)}>
<div className="dropdown-icon">
<IconCode size={16} strokeWidth={2} />
</div>
<div className="dropdown-label">gRPC</div>
</div>
<div className="dropdown-item" onClick={() => handleItemClick(REQUEST_TYPE.WEBSOCKET)}>
<div className="dropdown-icon">
<IconPlugConnected size={16} strokeWidth={2} />
</div>
<div className="dropdown-label">WebSocket</div>
</div>
</Dropdown>
);
};
export default CreateTransientRequest;

View File

@@ -38,20 +38,6 @@ const StyledWrapper = styled.div`
gap: 8px;
}
.control-button {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
transition: all 0.2s ease;
}
.debug-content {
flex: 1;
overflow: hidden;

View File

@@ -32,12 +32,6 @@ const StyledWrapper = styled.div`
}
}
.network-controls {
display: flex;
align-items: center;
gap: 8px;
}
.network-content {
flex: 1;
overflow: hidden;
@@ -81,11 +75,10 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 8px 16px;
padding: 4px 16px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
font-size: 10px;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -103,8 +96,7 @@ const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 80px 80px 150px 1fr 100px 80px 80px;
gap: 12px;
padding: 6px 16px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
padding: 2px 16px;
cursor: pointer;
transition: background-color 0.1s ease;
font-size: ${(props) => props.theme.font.size.sm};
@@ -115,7 +107,8 @@ const StyledWrapper = styled.div`
}
&.selected {
background: ${(props) => props.theme.console.buttonHoverBg};
padding-left: 13px;
background: ${(props) => props.theme.console.logHoverBg};
border-left: 3px solid ${(props) => props.theme.console.checkboxColor};
}
}
@@ -123,25 +116,19 @@ const StyledWrapper = styled.div`
.method-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 6px;
border-radius: 4px;
justify-content: start;
font-size: 10px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 45px;
}
.status-badge {
font-weight: 500;
font-size: ${(props) => props.theme.font.size.sm};
}
.request-domain {
color: ${(props) => props.theme.console.messageColor};
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -174,120 +161,6 @@ const StyledWrapper = styled.div`
font-size: ${(props) => props.theme.font.size.xs};
text-align: right;
}
.filter-dropdown {
position: relative;
}
.filter-dropdown-trigger {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
}
.filter-summary {
font-weight: 500;
min-width: 24px;
text-align: center;
}
}
.filter-dropdown-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 200px;
max-width: 250px;
background: ${(props) => props.theme.console.dropdownBg};
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow: hidden;
}
.filter-dropdown-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: ${(props) => props.theme.console.dropdownHeaderBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
color: ${(props) => props.theme.console.titleColor};
}
.filter-toggle-all {
background: transparent;
border: none;
color: ${(props) => props.theme.console.buttonColor};
cursor: pointer;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
padding: 2px 4px;
border-radius: 2px;
transition: all 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
}
}
.filter-dropdown-options {
padding: 4px 0;
}
.filter-option {
display: flex;
align-items: center;
padding: 6px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.optionHoverBg};
}
input[type="checkbox"] {
margin: 0 8px 0 0;
width: 14px;
height: 14px;
accent-color: ${(props) => props.theme.console.checkboxColor};
}
}
.filter-option-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.filter-option-label {
color: ${(props) => props.theme.console.optionLabelColor};
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 400;
}
.filter-option-count {
color: ${(props) => props.theme.console.optionCountColor};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 400;
margin-left: auto;
}
`;
export default StyledWrapper;

View File

@@ -1,128 +1,33 @@
import React, { useState, useRef, useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
IconFilter,
IconChevronDown,
IconNetwork
} from '@tabler/icons';
import {
updateNetworkFilter,
toggleAllNetworkFilters,
setSelectedRequest
} from 'providers/ReduxStore/slices/logs';
import StyledWrapper from './StyledWrapper';
const MethodBadge = ({ method }) => {
const getMethodColor = (method) => {
switch (method?.toUpperCase()) {
case 'GET': return '#10b981';
case 'POST': return '#8b5cf6';
case 'PUT': return '#f59e0b';
case 'DELETE': return '#ef4444';
case 'PATCH': return '#06b6d4';
case 'HEAD': return '#6b7280';
case 'OPTIONS': return '#84cc16';
default: return '#6b7280';
}
};
const methodLower = method?.toLowerCase() || 'get';
return (
<span
className="method-badge"
style={{ backgroundColor: getMethodColor(method) }}
>
<span className={`method-badge ${methodLower}`}>
{method?.toUpperCase() || 'GET'}
</span>
);
};
const StatusBadge = ({ status, statusCode }) => {
const getStatusColor = (code) => {
if (code >= 200 && code < 300) return '#10b981';
if (code >= 300 && code < 400) return '#f59e0b';
if (code >= 400 && code < 500) return '#ef4444';
if (code >= 500) return '#dc2626';
return '#6b7280';
};
const displayStatus = statusCode || status;
return (
<span
className="status-badge"
style={{ color: getStatusColor(statusCode) }}
>
<span className="status-badge">
{displayStatus}
</span>
);
};
const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggleAll }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const allFiltersEnabled = Object.values(filters).every((f) => f);
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className="filter-dropdown" ref={dropdownRef}>
<button
className="filter-dropdown-trigger"
onClick={() => setIsOpen(!isOpen)}
title="Filter requests by method"
>
<IconFilter size={16} strokeWidth={1.5} />
<span className="filter-summary">
{activeFilters.length === Object.keys(filters).length ? 'All' : `${activeFilters.length}/${Object.keys(filters).length}`}
</span>
<IconChevronDown size={14} strokeWidth={1.5} />
</button>
{isOpen && (
<div className="filter-dropdown-menu right">
<div className="filter-dropdown-header">
<span>Filter by Method</span>
<button
className="filter-toggle-all"
onClick={() => onToggleAll(!allFiltersEnabled)}
>
{allFiltersEnabled ? 'Hide All' : 'Show All'}
</button>
</div>
<div className="filter-dropdown-options">
{Object.keys(filters).map((method) => (
<label key={method} className="filter-option">
<input
type="checkbox"
checked={filters[method]}
onChange={(e) => onFilterToggle(method, e.target.checked)}
/>
<div className="filter-option-content">
<MethodBadge method={method} />
<span className="filter-option-label">{method}</span>
<span className="filter-option-count">({requestCounts[method] || 0})</span>
</div>
</label>
))}
</div>
</div>
)}
</div>
);
};
const RequestRow = ({ request, isSelected, onClick }) => {
const { data } = request;
const { request: req, response: res, timestamp } = data;
@@ -241,22 +146,6 @@ const NetworkTab = () => {
});
}, [allRequests, networkFilters]);
const requestCounts = useMemo(() => {
return allRequests.reduce((counts, request) => {
const method = request.data?.request?.method?.toUpperCase() || 'GET';
counts[method] = (counts[method] || 0) + 1;
return counts;
}, {});
}, [allRequests]);
const handleFilterToggle = (method, enabled) => {
dispatch(updateNetworkFilter({ method, enabled }));
};
const handleToggleAllFilters = (enabled) => {
dispatch(toggleAllNetworkFilters(enabled));
};
const handleRequestClick = (request) => {
dispatch(setSelectedRequest(request));
};

View File

@@ -15,7 +15,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
padding: 2px 8px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
flex-shrink: 0;
@@ -27,7 +27,6 @@ const StyledWrapper = styled.div`
gap: 8px;
color: ${(props) => props.theme.console.titleColor};
font-size: ${(props) => props.theme.font.size.base};
font-weight: 500;
.request-time {
color: ${(props) => props.theme.console.countColor};
@@ -66,7 +65,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
padding: 4px 8px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -92,7 +91,7 @@ const StyledWrapper = styled.div`
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 16px;
padding: 8px;
min-height: 0;
height: 0;
}
@@ -170,8 +169,7 @@ const StyledWrapper = styled.div`
z-index: 10;
td {
padding: 8px 12px;
font-weight: 500;
padding: 4px 8px;
color: ${(props) => props.theme.console.titleColor};
text-transform: uppercase;
font-size: ${(props) => props.theme.font.size.xs};
@@ -198,7 +196,7 @@ const StyledWrapper = styled.div`
}
td {
padding: 8px 12px;
padding: 2px 8px;
vertical-align: top;
word-break: break-word;
}
@@ -256,7 +254,7 @@ const StyledWrapper = styled.div`
}
.response-body-container {
border-radius: 4px;
border-radius: ${(props) => props.theme.border.radius.sm};
overflow: hidden;
height: 400px;
display: flex;
@@ -270,7 +268,7 @@ const StyledWrapper = styled.div`
}
div[role="tablist"] {
padding: 8px 12px;
padding: 4px 8px;
border-bottom: 1px solid ${(props) => props.theme.console.border};
display: flex !important;
gap: 8px !important;
@@ -305,7 +303,7 @@ const StyledWrapper = styled.div`
}
}
.network-logs-container {
.network-logs-wrapper {
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
overflow: hidden;
@@ -313,17 +311,17 @@ const StyledWrapper = styled.div`
min-height: 200px;
max-height: 400px;
.network-logs {
.network-logs-container {
background: ${(props) => props.theme.console.contentBg} !important;
color: ${(props) => props.theme.console.messageColor} !important;
height: 100% !important;
max-height: 400px !important;
pre {
padding: 0.5rem !important;
.network-logs-pre {
color: ${(props) => props.theme.console.messageColor} !important;
font-size: ${(props) => props.theme.font.size.xs} !important;
line-height: 1.4 !important;
padding: 12px !important;
}
}
}

View File

@@ -141,7 +141,7 @@ const NetworkTab = ({ response }) => {
<div className="tab-content">
<div className="section">
<h4>Network Logs</h4>
<div className="network-logs-container">
<div className="network-logs-wrapper">
{timeline.length > 0 ? (
<Network logs={timeline} />
) : (

View File

@@ -13,7 +13,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
padding: 0 8px;
background: ${(props) => props.theme.console.headerBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
flex-shrink: 0;
@@ -30,7 +30,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
padding: 4px 8px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
@@ -38,8 +38,6 @@ const StyledWrapper = styled.div`
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
border-radius: 4px 4px 0 0;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
@@ -47,9 +45,9 @@ const StyledWrapper = styled.div`
}
&.active {
color: ${(props) => props.theme.console.checkboxColor};
border-bottom-color: ${(props) => props.theme.console.checkboxColor};
background: ${(props) => props.theme.console.contentBg};
color: ${(props) => props.theme.primary.strong};
border-bottom-color: ${(props) => props.theme.primary.strong};
background: ${(props) => props.theme.background.mantle};
}
}
@@ -144,9 +142,6 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 4px;
margin-right: 8px;
padding-right: 8px;
border-right: 1px solid ${(props) => props.theme.console.border};
}
.action-controls {
@@ -159,23 +154,21 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
width: 24px;
height: 24px;
background: transparent;
border: none;
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
background: ${(props) => props.theme.background.surface0};
}
&.close-button:hover {
background: #e81123;
color: white;
background: ${(props) => props.theme.background.surface0};
}
}
@@ -187,19 +180,17 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
padding: 2px 8px;
background: transparent;
border: 1px solid ${(props) => props.theme.console.border};
border-radius: 4px;
color: ${(props) => props.theme.console.buttonColor};
border: 1px solid ${(props) => props.theme.border.border0};
border-radius: ${(props) => props.theme.border.radius.sm};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.2s ease;
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.console.buttonHoverBg};
color: ${(props) => props.theme.console.buttonHoverColor};
border-color: ${(props) => props.theme.console.border};
background: ${(props) => props.theme.background.surface0};
}
.filter-summary {
@@ -232,7 +223,7 @@ const StyledWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
padding: 4px 12px;
background: ${(props) => props.theme.console.dropdownHeaderBg};
border-bottom: 1px solid ${(props) => props.theme.console.border};
font-size: ${(props) => props.theme.font.size.sm};
@@ -263,7 +254,7 @@ const StyledWrapper = styled.div`
.filter-option {
display: flex;
align-items: center;
padding: 6px 12px;
padding: 4px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
@@ -325,20 +316,6 @@ const StyledWrapper = styled.div`
.logs-container {
padding: 8px 0;
}
.method-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.5px;
min-width: 45px;
}
.log-entry {
display: flex;

View File

@@ -5,10 +5,9 @@ import ToolHint from 'components/ToolHint/index';
const StyledSessionList = styled.div`
.session-list-item {
padding: 10px 12px;
padding: 2px 6px;
cursor: pointer;
border-bottom: 1px solid ${(props) => props.theme.border || 'rgba(255, 255, 255, 0.05)'};
transition: all 0.2s;
display: flex;
flex-direction: column;
gap: 4px;
@@ -24,7 +23,8 @@ const StyledSessionList = styled.div`
&.active {
background: ${(props) => props.theme.sidebarActive || 'rgba(59, 142, 234, 0.12)'};
border-left: 2px solid ${(props) => props.theme.brandColor || '#3b8eea'};
border-left: 2px solid ${(props) => props.theme.primary.subtle};
padding-left: 4px;
}
&:last-child {

View File

@@ -30,7 +30,7 @@ const StyledWrapper = styled.div`
}
.terminal-sessions-header {
padding: 12px;
padding: 6px 8px;
font-weight: 600;
font-size: 13px;
color: ${(props) => props.theme.text};

View File

@@ -2,10 +2,37 @@ import React, { useRef, useEffect, useState, useCallback } from 'react';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { IconTerminal2, IconPlus } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
import SessionList from './SessionList';
import '@xterm/xterm/css/xterm.css';
// Build xterm.js theme from app theme
const getTerminalTheme = (theme) => {
return {
background: theme.console.bg,
foreground: theme.console.messageColor,
cursor: theme.console.messageColor,
selectionBackground: theme.status.info.background,
black: theme.background.base,
red: theme.status.danger.text,
green: theme.status.success.text,
yellow: theme.status.warning.text,
blue: theme.status.info.text,
magenta: theme.colors.text.purple,
cyan: theme.codemirror.variable.prompt,
white: theme.text,
brightBlack: theme.colors.text.muted,
brightRed: theme.status.danger.text,
brightGreen: theme.status.success.text,
brightYellow: theme.status.warning.text,
brightBlue: theme.status.info.text,
brightMagenta: theme.colors.text.purple,
brightCyan: theme.codemirror.variable.prompt,
brightWhite: theme.text
};
};
// Terminal instances per session - Map<sessionId, { terminal, fitAddon, inputDisposable, resizeDisposable }>
const terminalInstances = new Map();
@@ -33,7 +60,7 @@ const ensureParkingHost = () => {
return parkingHost;
};
const createTerminalForSession = (sessionId) => {
const createTerminalForSession = (sessionId, terminalTheme) => {
if (terminalInstances.has(sessionId)) {
return terminalInstances.get(sessionId);
}
@@ -42,28 +69,7 @@ const createTerminalForSession = (sessionId) => {
cursorBlink: true,
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#d4d4d4',
selection: '#264f78',
black: '#1e1e1e',
red: '#f14c4c',
green: '#23d18b',
yellow: '#f5f543',
blue: '#3b8eea',
magenta: '#d670d6',
cyan: '#29b8db',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightMagenta: '#d670d6',
brightCyan: '#29b8db',
brightWhite: '#e5e5e5'
},
theme: terminalTheme,
allowProposedApi: true
});
@@ -156,10 +162,10 @@ const cleanupTerminalInstance = (sessionId) => {
}
};
const openTerminalIntoContainer = async (container, sessionId) => {
const openTerminalIntoContainer = async (container, sessionId, terminalTheme) => {
if (!container || !sessionId) return;
const instance = createTerminalForSession(sessionId);
const instance = createTerminalForSession(sessionId, terminalTheme);
const { terminal, fitAddon } = instance;
if (!terminal.element) {
@@ -174,6 +180,7 @@ const openTerminalIntoContainer = async (container, sessionId) => {
await new Promise((resolve) => setTimeout(resolve, 50));
try {
fitAddon.fit();
terminal.focus();
const { cols, rows } = terminal;
if (cols && rows && window.ipcRenderer) {
window.ipcRenderer.send('terminal:resize', sessionId, { cols, rows });
@@ -211,6 +218,8 @@ const TerminalTab = () => {
const [sessions, setSessions] = useState([]);
const [activeSessionId, setActiveSessionId] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const { theme } = useTheme();
const terminalTheme = getTerminalTheme(theme);
// Load sessions list
const loadSessions = useCallback(async (currentActiveSessionId = null) => {
@@ -354,6 +363,15 @@ const TerminalTab = () => {
};
}, []);
// Update all terminal themes when app theme changes
useEffect(() => {
terminalInstances.forEach((instance) => {
if (instance.terminal) {
instance.terminal.options.theme = terminalTheme;
}
});
}, [theme.mode]);
// Handle terminal display for active session
useEffect(() => {
if (!activeSessionId || !terminalRef.current) return;
@@ -361,7 +379,7 @@ const TerminalTab = () => {
let mounted = true;
const setupTerminal = async () => {
await openTerminalIntoContainer(terminalRef.current, activeSessionId);
await openTerminalIntoContainer(terminalRef.current, activeSessionId, terminalTheme);
if (mounted) {
const instance = terminalInstances.get(activeSessionId);

Some files were not shown because too many files have changed in this diff Show More