Compare commits

..

525 Commits

Author SHA1 Message Date
Cmarvin1
148d3f0e7d fix: Code Generation for Basic Auth (#6474)
* 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 18:37:49 +05:30
Pooja
75e17610f0 fix: openapi query param import (#6241) 2026-01-21 17:53:14 +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
Bijin A B
b41f4974f9 chore: playwright fix (#6507) 2025-12-25 15:16:34 +05:30
naman-bruno
2446301e41 use: button component (#6504)
* use: button component

* fixes
2025-12-25 12:33:49 +05:30
naman-bruno
67903f26bc export & import in opencollection format (#6329) 2025-12-24 22:28:38 +05:30
Abhishek S Lal
1b8eece173 Add right-click context menu to request tabs with MenuDropdown # (#6502)
* refactor: replace Dropdown with MenuDropdown in RequestTab component; update Dropdown props handling in Dropdown component

* refactor: remove Portal import and simplify menuDropdown rendering in RequestTab component

* refactor: streamline RequestTabMenu functionality and improve tab closing methods with async handling

* refactor: enhance Dropdown and MenuDropdown components with improved props handling and styling adjustments

* refactor: enhance Dropdown and MenuDropdown components by improving structure and removing unused styles

* refactor: update Dropdown and MenuDropdown components to append to sidebar sections container for improved layout

* refactor: integrate dropdownContainerRef for improved MenuDropdown positioning in RequestTabs and Sidebar components

* refactor: update Dropdown component to include 'tippy-box' class for e2e test selections

* refactor: update dropdown item selection logic in selectRequestPaneTab function for improved accuracy

* refactor: add fixed positioning to popperOptions in Collection and CollectionItem components for improved dropdown behavior

---------

Co-authored-by: sanjai <sanjai@usebruno.com>
2025-12-24 21:08:53 +05:30
Pooja
1f05ffd469 fix: pasting request ito parent folder even if request is selected (#6446) 2025-12-24 12:14:37 +05:30
Anoop M D
c2acc25461 Merge pull request #6498 from usebruno/feat/button-storybook
feat: button storybook
2025-12-24 05:52:09 +05:30
Anoop M D
dc9df80638 feat: update button component with new rounded options and story 2025-12-24 05:51:32 +05:30
Anoop M D
c5abe4122b feat: button storybook 2025-12-24 05:30:04 +05:30
naman-bruno
3081c06964 update: modal styles (#6487)
* update modals styles

* chore: color and style improvements

* fix: tests

* fixes: tests

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-12-23 23:29:03 +05:30
naman-bruno
8c7ed3fe51 improve: workspace handling (#6495)
* improve: workspace

* fixes
2025-12-23 20:22:51 +05:30
Pooja
ce33cee03d fix: autosave (#6392)
* fix: autosave

* rm: console
2025-12-23 19:21:56 +05:30
Abhishek S Lal
d93d1eacdb refactor: centralize tab management (#6494)
* refactor: centralize tab management by removing redundant hide calls in Collection components

- Removed dispatch calls for hiding home and API spec pages from Collection and CollectionItem components.
- Added logic in app slice to automatically hide these pages when a tab is added or focused, improving code maintainability.

* refactor: remove redundant hideHomePage dispatches from components
2025-12-23 19:19:25 +05:30
Abhishek S Lal
aeb6b12b06 fix: update SensitiveFieldWarning prop name for consistency in WsseAuth component (#6492) 2025-12-23 17:56:46 +05:30
lohit
41ed51b4e3 fix: handle additional context root paths for node-vm (#6491)
* fix: handle additional context root paths for node vm

* fix: handle additional context root paths for node vm

* fix: coderabbit review fixes
2025-12-23 17:31:51 +05:30
Sid
b85f60e1d6 fix: prevent double serialization of websocket text messages. (#6182) (#6479)
* fix: prevent double serialization of websocket text messages. (#6182)

* fix: improve websocket message handling and serialization

- Added normalization for message format to prevent double encoding.
- Updated queueMessage and sendMessage methods to handle message format.
- Refactored code for better readability and maintainability.

fix: enhance message normalization in WebSocket client

---------

Co-authored-by: Praveen kumar <praveenkumar042023@gmail.com>
2025-12-23 17:08:30 +05:30
naman-bruno
49ffdd1b8f fix: linux titlebar (#6483) 2025-12-23 16:54:30 +05:30
Abhishek S Lal
f1961a8988 refactor: update ResponsePane and QueryResultTypeSelector (#6490)
* refactor: update ResponsePane and QueryResultTypeSelector for improved tab handling and styling

- Adjusted the expanded width for right-side action buttons in ResponsePane.
- Refactored view tab toggle logic to enhance clarity and functionality.
- Introduced new styling for result view tabs and dropdown buttons.
- Added icon support for format options in QueryResultTypeSelector, improving visual feedback.
- Implemented dropdown state management to ensure proper interaction with active tabs.

* refactor: remove console log from ResponsePane for cleaner code
2025-12-23 16:38:24 +05:30
lohit
4831434e37 fix: oauth2 url update (#6489) 2025-12-23 16:00:17 +05:30
Sanjai Kumar
87c8934c45 fix: update stringifyHttpRequest to handle response body content correctly (#6488) 2025-12-23 15:48:52 +05:30
Pooja
01d4d3dc2a fix: run formatResponse execution on copy button click (#6485) 2025-12-23 14:25:18 +05:30
naman-bruno
70178f60b3 add: rename folder option while creating workspace (#6481) 2025-12-22 18:29:33 +05:30
Abhishek S Lal
cba164bc9b fix: update selectedTab prop to use selectedViewTab in ResponsePane component (#6478) 2025-12-22 14:58:54 +05:30
Pooja
669c99f40a fix: copy response based on preview toggle and selected format (#6436) 2025-12-22 13:53:08 +05:30
Abhishek S Lal
9967d863f5 feat: enhance ResponsePane with persisted response format and view tab state management (#6475)
- Added Redux state management for response format and view tab in ResponsePane.
- Implemented useCallback hooks for handling format changes and view tab toggling.
- Updated component to utilize persisted values from Redux, improving user experience by maintaining state across sessions.
2025-12-22 13:51:19 +05:30
Abhishek S Lal
3552801ca5 fix: refactor default tab selection logic for correct tab persistance (#6473) 2025-12-22 13:40:51 +05:30
Pooja
6f2804ea0f fix: var into tooltip for faker vars (#6312)
* fix: var into tooltip for faker vars

* fix: oauth variable

* rm: test

* rm: comment
2025-12-22 13:02:51 +05:30
Pragadesh-45
41efa8505b fix: restrict keyboard event handling to modal elements only (#6408)
* fix: restrict keyboard event handling to modal elements only

* chore: minor refactor

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-19 19:01:08 +05:30
Thomas
f47e9e9304 Add file attribute to Junit testsuite report (#6425)
* feat: add file attribute to Junit testsuite report

* test: update tests to include file attribute

* fix: update playwright tests to support the new file attribute

---------

Co-authored-by: Thomas Vackier <thomas.vackier@inthepocket.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-19 19:00:15 +05:30
Pooja
5f88e7d201 fix: auto-expand collapsed sidebar section when clicking action buttons (#6468) 2025-12-19 16:15:52 +05:30
Chirag Chandrashekhar
e4e17b0c74 fix: reverted the easy creation flow to the old, modal based approach (#6449)
* fix: reverted the easy creation flow to the old, modal based approach

* fix: updated the tests to use the old createRequest action and removed the usage of createUntitledRequest

* removed safe mode selection after collection open
2025-12-19 16:13:47 +05:30
Sid
83feffd41d chore: have setup install all deps (#6421)
* chore: have setup install all deps

* Update scripts/setup.js

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

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-19 16:05:28 +05:30
Chirag Chandrashekhar
7d783d473f feat: ability to open terminal at the root of a workspace (#6467)
* enhancement: ability to open terminal at the root of a workspace

* fix: imports

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-19 15:21:28 +05:30
dependabot[bot]
6a177e17d3 chore(deps): bump actions/upload-artifact from 5 to 6 (#6416)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 13:59:12 +05:30
Chirag Chandrashekhar
3552e7e609 Fix: Terminal Resize (#6448)
* fix: added watcher on parent div resize and window resize and calls fit() on both events

* removed effects and moved the resize handling to ref callback

* fix: added ResizeObserver cleanup

* fix: improve terminal resizing logic

- Refactored fit logic to avoid unnecessary calls during hidden states.
- Enhanced error handling when creating new terminal sessions.
- Updated ResizeObserver to ensure proper fitting on container resize.

* fix: remove unused fitRafRef in TerminalTab component

* fix: refactor terminal mount logic to use a dedicated callback

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-19 12:34:11 +05:30
Chirag Chandrashekhar
7164119695 fix: updated the selected state colors of tabs in dev tools (#6465) 2025-12-19 02:36:30 +05:30
Anoop M D
6a05b04676 Merge pull request #6463 from usebruno/feat/design-tweaks
feat: design tweaks
2025-12-19 00:32:49 +05:30
Anoop M D
8c1975ba7b feat: design tweaks 2025-12-19 00:32:02 +05:30
naman-bruno
397ccbb425 fix: export/import icons and styles (#6462) 2025-12-19 00:00:12 +05:30
Anoop M D
336496a1d7 Merge pull request #6461 from usebruno/feat/opencollection-presets
feat: opencollection presets
2025-12-18 22:43:38 +05:30
Anoop M D
aadbf8c33f feat: opencollection presets 2025-12-18 22:33:35 +05:30
Anoop M D
c5827dfa72 feat: opencollection actions (#6460) 2025-12-18 21:21:54 +05:30
Anoop M D
9738a2afb7 feat: opencollection actions 2025-12-18 21:19:29 +05:30
naman-bruno
a1c4113897 add: workspace tabs (#6456)
* add: workspace tabs

* fixes

* fixes
2025-12-18 21:01:58 +05:30
sanish chirayath
052d143d6e fix: example icon color (#6447)
* fix: example icon color

fix: example color

* fix: indentation

* fix: use gray color from colors for example

* fix:  margin  issues
2025-12-18 20:37:59 +05:30
Siddharth Gelera (reaper)
aac219d4cd fix: move gql variables prettify icon to a better position (#6455)
* move gql variable prettier icon down

* fix: doesn't need the class
2025-12-18 19:47:16 +05:30
sanish chirayath
b188a9e9a9 fix: unable to add assertions to a request (#6435)
* fix: add assertion

* rm: unnecessary wait fn

* fix: test

* fix: tests

* fix: review comments

* fix: review

* fix: review comments

* fix: review comments

* fix: test failure

* review fixes

* fix: rm sandbox accept

* fix: indentation
2025-12-18 19:37:33 +05:30
naman-bruno
6ab8fcb710 fix: duplicate message on workspace rename and close (#6457) 2025-12-18 18:48:57 +05:30
Sanjai Kumar
1cc117ceb9 fix: crash when saving empty GraphQL query in YAML collections (#6453)
* fix: ensure GraphQL variables are handled correctly in multiple components

* fix: reverted some changes

* chore: temp fix for tests

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-18 18:37:48 +05:30
Abhishek S Lal
62b8784972 feat: add hideResultTypeSelector prop to QueryResponse component (#6452)
* Introduced hideResultTypeSelector prop to conditionally render the QueryResultTypeSelector in the QueryResponse component.
* Updated BodyBlock to pass the type prop to control the visibility of the result type selector based on the request or response context.
* Adjusted styling in StyledWrapper for improved layout consistency.
2025-12-18 17:48:53 +05:30
Pooja
5e6444b8b5 feat: Set JavaScript sandbox to safe mode by default for new collections (#4824)
* feat: Set JavaScript sandbox to safe mode by default for new collections

* rm: sandbox code in playwright test

* rm: safe mode code in var interpolation test

* rm: sandbox modal code

* fix

* fix

* fix

* fix

* improve

* improvement

* fix

* fix
2025-12-18 17:27:38 +05:30
naman-bruno
bc2efb9686 Environment's as tabs (#6407)
* add: env's as tabs

* fix: test

* fix: tests

* fixes

* fix: test

* fixes

* fixes

* fix

* fix: styling

* fixes
2025-12-18 16:10:00 +05:30
Chirag Chandrashekhar
678fa88a7c perf: linkAware slow in large files (#6422)
* bugfix: linkAware slow in large files
- Added link detection and class addition operations in an editor.operation block for atomic operations and prevent multiple small rerenders.
- linkAware works on currently visible lines in the viewport. This is to speedup linkAware and defer detection for lines not in viewport.
- linkAware now runs after initial render and not before it. This ensures that we calculate to the lines on viewport and does not pause render.

* test(bruno-app): fix linkAware spec for debounced viewport marking
2025-12-18 15:52:04 +05:30
naman-bruno
80e09d1a26 fix: opencollection export as bruno json (#6444) 2025-12-18 14:24:45 +05:30
Abhishek S Lal
78ee99eab9 Fix/app titlebar windows (#6437)
* style: Update padding and margin in StyledWrapper for improved layout; adjust ActionIcon size in ResponseLayoutToggle for better UI consistency; enhance title bar color handling in Electron app

* feat: Enhance AppTitleBar with Windows-specific controls and OS detection

* refactor: Improve OS detection and error handling in AppTitleBar; streamline maximize state management

* feat: Implement IPC communication for maximize/unmaximize events in AppTitleBar; enhance state management in Electron main process
2025-12-17 21:40:24 +05:30
naman-bruno
73124fd715 add: manage workspace (#6424)
* add: manage workspace

* fixes

* replace dropdown to MenuDropdown

* rm: refs
2025-12-17 20:35:18 +05:30
naman-bruno
4c1fba611a fix: close all collection in workspace (#6434)
* fix: close all collection in workspace

* move: function
2025-12-17 18:52:17 +05:30
naman-bruno
3cfbf890ac add: export & import of workspace as zip (#6432)
* init

* fix

* update: package lock
2025-12-17 18:49:02 +05:30
Sanjai Kumar
395aa4246e fix: OpenAPI import fails when securitySchemes are not defined (#6429)
* feat: enhance OpenAPI security scheme handling

* refactor: revert test changes and update openapi-to-bruno
2025-12-17 16:52:54 +05:30
Pooja
639c8e573f fix: response pane size when devtool open (#6380) 2025-12-17 12:15:11 +05:30
Bijin A B
7d317a775b fix(playwright): interpolate request url with odata param (#6428) 2025-12-17 12:14:42 +05:30
Timon
2eb8db9b45 fix: Only update scroll position when unmounting the editor (#6420)
before the scroll position was updatet on every scroll, causing
everything related to the tab to rerender.
2025-12-16 18:34:29 +05:30
Abhishek S Lal
30d2a6d141 Refactor dropdown components to use MenuDropdown for improved functionality and keyboard accessibility (#6404)
* Refactor dropdown components to use MenuDropdown for improved functionality and keyboard accessibility

- Replaced Dropdown with MenuDropdown in various components including BodyModeSelector, AuthMode, and RequestBodyMode.
- Updated styles and structure for better usability and accessibility.
- Removed unused Dropdown component and its associated styles.
- Enhanced action buttons in ResponsePane and Collection components with ActionIcon for better UI consistency.

* fix: Update HttpMethodSelector styles and tests for improved accessibility

- Changed the class name for the "Add Custom" button to include 'text-link' for better styling.
- Updated tests to use role-based queries for dropdown items, enhancing accessibility checks.
- Ensured the correct application of classes in tests to reflect the updated structure.

* refactor: Improve component accessibility and consistency

* fix: update hover behavior for collection actions menu in runner.ts

* refactor: streamline hover interactions for collection actions across tests

* refactor: enhance component structure and accessibility across response actions

* fix: correct fill property syntax in StyledWrapper for consistent styling

* refactor: simplify isDisabled logic in response components for clarity

* fix: correct tabIndex logic in ResponseCopy component for improved accessibility

* fix: update tabIndex logic in ResponseBookmark component for improved accessibility

* fix: enable action buttons in ResponsePaneActions for improved usability

* refactor: remove unnecessary tabIndex attributes in response components for improved accessibility

* refactor: remove keyDown event handlers from response components for cleaner interaction

* refactor: remove SidebarHeader component and related styles for improved structure
2025-12-16 18:26:38 +05:30
lohit
231776ca4b feat: use default browser for oauth2 authorization bru-2167 (#6101)
* feat: use default browser for oauth2 authorization bru-2167

* fix: coderabbit review comment fixes

* fix: coderabbit review comment fixes

* fix: protocol registration updates

* fix: coderabbit review comment suggestions

* fix: oauth2 auth form use system browser option
2025-12-16 17:23:49 +05:30
Pooja
dbd966850c fix: openapi body import (#6288)
* fix: openapi body import

* add: unit test

* fix

* fix

* Revert "fix"

This reverts commit 3219e8af8e.

* fix: we need the same check here too!

* fix: handle number type

* fix: correct empty securitySchemes check

---------

Co-authored-by: Taylore Thornton <tthornton3@chewy.com>
2025-12-16 17:23:22 +05:30
Pooja
dc111ecce2 add: presets in collection setting (#6389)
* add: presets in collection setting

* fix

* add: websocket in preset

* fix: htmlFor
2025-12-16 17:16:41 +05:30
Pooja
fdff792476 feat: add support for ssl cert in websockt (#6286)
* feat: add support for ssl cert in websockt

* improvements

* add: wss in animation

* fix: avoid a race condition between the locator's promise and the expect call

JS starts resolving promises even without the await unless it's a function, this can cause a race in this case

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-16 17:12:47 +05:30
Bijin A B
a9c63e6f2a Revert "Save cookies on redirect response (#6094)" (#6413)
This reverts commit 1b9ea478da.
2025-12-15 20:10:06 +05:30
Abhishek S Lal
014817810d Fix/response pane optimizations (#6395)
* refactor: update content type detection to use base64 decoding

* fix: some styling issues and autofocus issues in input resolved

* refactor: enhance ResponsePane and QueryResult components for improved response handling and size display

* refactor: simplify size display logic in ResponseSize component

* refactor: improve size formatting logic in ResponseSize component for better readability

* refactor: enhance base64 decoding function to handle invalid input and improve error handling
2025-12-15 19:32:57 +05:30
Pragadesh-45
71cf1a8f26 fix: include request URL in prompt variable extraction and add tests (#6412) 2025-12-15 19:13:33 +05:30
naman-bruno
a769ca3ae4 fix: tabs z-index issue (#6411) 2025-12-15 18:22:08 +05:30
naman-bruno
3d61106cc1 fix: rename crash (#6410) 2025-12-15 18:20:36 +05:30
naman-bruno
6cc114100f fix (#6409) 2025-12-15 18:17:11 +05:30
Abhishek S Lal
c11266a96f fix: Improved logic for determining right side expandability of Response Actions (#6398)
* fix: Improved logic for determining right side expandability based on container width and provided width.

* refactor: Standardize naming for right-side expandability a

* refactor: Simplify collection interaction in keyboard shortcuts tests

* refactor: Update right-side expandability logic in ResponsiveTabs and StyledWrapper components
2025-12-15 16:22:32 +05:30
naman-bruno
8b0f41e3cb fix: default workspace error checking (#6379)
* fix: default workspace error checking

* add: tests

* fixes

* fix

* fixes

* fixes

* fix

* fixes

* fix

* chore: close app context in tests

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-15 15:10:53 +05:30
Jeroen Vinke
1b9ea478da Save cookies on redirect response (#6094)
Co-authored-by: Jeroen Vinke <jeroen.vinke@iddinkgroup.com>
2025-12-15 14:08:20 +05:30
sanish chirayath
8cbda5f5cc fix: refactor response examples to use MenuDropdown and Editable components (#6382)
* feat: use common dropdown component

* fix: update example ui to match v3

* fix: test cases, bugs

* fix: review comments

* fix: review comments

* fix: review

* fix: file body/binary table within response examples

* fix: file name, close btn not visible issue

* fix: unnessary transition for three  dots

* fix: install missing deps in bruno-app

* update example url when param is updated

* empty commit

* chore: update package-lock.json

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-14 16:21:06 +05:30
sanish chirayath
2f5537c8db Enhance file watching by ensuring 'node_modules' and '.git' are always ignored (#6391)
* Enhance file watching by ensuring 'node_modules' and '.git' are always ignored

* fix: tests

* rm: dialog assignments

* fix:  duplication
2025-12-12 19:04:52 +05:30
Abhishek S Lal
2327b21c85 Feat/response tabs rewamp (#6388)
* refactor: used common component for layout switching button

* refactor: replace RequestPaneTabs with ResponsiveTabs component across RequestPane and HttpRequestPane

* refactor: simplify ResponsePaneActions component and improve layout handling

* refactor: enhance ResponsePane component with improved tab handling and layout adjustments

* refactor: update layout toggle functionality and button labels in ResponsePane components

* refactor: ensure consistent action button selection in response actions
2025-12-12 17:33:07 +05:30
max-melhuish-depop
6652cca642 Removed filtering of empty strings from url paths when importing from postman collection (#5868)
* removed filtering of empty strings from url paths when importing from postman collection

* revert accidental non-pr changes

* chore: remove console logs

---------

Co-authored-by: Max Melhuish <238188923+max-melhuish-depop@users.noreply.github.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-12 11:52:29 +05:30
naman-bruno
575f37124c fixes (#6383) 2025-12-11 19:49:27 +05:30
sanish chirayath
50a72a16bc fix: tag persistence tests (#6384) 2025-12-11 19:46:36 +05:30
sanish chirayath
98513c65f0 fix: gRPC oauth2 call is not taking ssl cert and proxy config (#6313)
* fix: grpc oauth2

* fix: review comments

* fix: review comments
2025-12-11 16:06:19 +05:30
sanish chirayath
b61d2212f6 fix: Consistent multipart form handling and @contentType support in examples (#6325)
* fix: multiline multipart items within multipart within response example

* change multiline  editor to single line fot contentType

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-11 01:17:39 +05:30
naman-bruno
1ed957978a Improve tables design (#6330) 2025-12-10 19:39:16 +05:30
naman-bruno
c00cbf6cb2 workspace schema update (#6374) 2025-12-10 19:38:47 +05:30
Abhishek S Lal
632f8705e5 feat: implement sidebar accordion sections (#6373)
* feat: implement sidebar accordion sections

- Added SidebarAccordionContext for managing expanded sections.
- Introduced SidebarContent component to render sections dynamically.
- Created CollectionsSection and ApiSpecsSection for sidebar organization.
- Updated Sidebar component to utilize new sections and context.
- Enhanced StyledWrapper for improved layout and styling of sidebar sections.

* refactor: streamline Sidebar component and enhance styling

* feat: enhance SidebarSection with ActionIcon and improved hover styles

* fix: update useEffect dependencies in SidebarAccordionContext and enhance accessibility in SidebarSection

* style: increase gap in StyledWrapper and reintroduce cursor pointer for better user interaction

* style: remove custom scrollbar styles from Sidebar components for a cleaner look
2025-12-10 19:04:46 +05:30
naman-bruno
f8548225e1 fixes (#6372) 2025-12-10 17:43:35 +05:30
Anoop M D
7fe6b47aa0 chore: updated request tab padding (#6368)
* chore: updated request tab padding
2025-12-10 04:30:58 +05:30
naman-bruno
43f24ad0f1 redesign: workspace overview (#6361)
* redesign: workspace overview

* fixes

* fix: test
2025-12-10 02:56:28 +05:30
Abhishek S Lal
a798b32f25 feat: add response data type selector in response viewer (#6100)
* feat: add response data type selector in response viewer

* chore: fixed lint issue

* test: add test for resonse format change and preview.

* refactor: streamline response format tests with utility functions for navigation and format switching

* refactor: simplify ButtonDropdown component and enhance QueryResultTypeSelector with header and toggle switch

* feat: enhance ButtonDropdown with prefix and suffix props; implement content type detection and update QueryResult for improved format handling

* fix: lint errors resolved

* fix: remove unnecessary blank line to resolve lint issues

* fix: update response format tests

* refactor: remove preview tab locator from response format tests

* fix: update dependency in useEffect to include previewFormatOptions for accurate format handling

* refactor: reorganize imports and enhance QueryResult component for improved format handling and error display

* fix: update error messages in response format preview tests and adjust version in JSON fixture

* feat: add drag detection to HtmlPreview component and update structure for improved user interaction

* refactor: update ResponsePane components for improved structure and functionality;

replace QueryResult with QueryResponse, enhance layout handling, and streamline response actions

* refactor: remove ButtonDropdown component and associated styles;

* refactor: moved ErrorAlert to ui folder

* fix: lint error

* feat: add data-testid attributes to Collection and CollectionItem components for improved testability

* feat: hide dropdown on select in response selector

* fix: update QueryResult component to use detectedContentType for format handling

* test: update ResponseLayoutToggle tests to use data-testid for button selection

* feat: add data-testid attribute to ResponseClear component for improved testability

* refactor: implement clickResponseAction utility for streamlined response action handling in tests

* feat: add data-testid attribute to ResponseCopy component for enhanced testability

* fix: unwanted code in test
2025-12-09 23:45:01 +05:30
Bijin A B
4d1c3f9e52 chore: reduce ux conflicts with toasts in playwright (#6367) 2025-12-09 23:11:16 +05:30
Abhishek S Lal
879d2271b7 fix: update default state for advanced options and change default collection format (#6366) 2025-12-09 22:54:56 +05:30
naman-bruno
cf4c896431 improve: tabs design (#6363)
* improve: tabs design

* fixes: tests
2025-12-09 21:35:27 +05:30
Chirag Chandrashekhar
f6363389d0 Prototype/simplify request creation (#6295)
* feat: add dropdown for quick request creation in tab bar

- Create reusable CreateUntitledRequest component with customizable trigger
- Add generateUniqueRequestName utility for unique request naming
- Replace modal-based request creation with dropdown in tab bar
- Support HTTP, GraphQL, WebSocket, and gRPC request types
- Generate unique names (Untitled, Untitled1, etc.) automatically
- Create requests at collection root level

* Update request creation and collection components

* Fix dropdown positioning and styling when appended to document.body

- Change appendTo from 'parent' to document.body for absolute positioning
- Add comprehensive styling via onShow handler to ensure proper width, padding, text color, and opacity
- Add global styles as fallback for dropdown elements
- Ensure dropdown overlaps parent without expanding it

* Update RequestTabs and Collection components

* Add curl paste detection and parsing for HTTP requests

* Fix generateUniqueRequestName to check filesystem for existing files

* feat: add placeholder text to HTTP request URL input

Add helpful placeholder text 'Enter URL or paste a cURL request' to the HTTP request URL input field. This guides users on how to use the input field, indicating they can either enter a URL directly or paste a cURL command which will be automatically parsed.

* Simplify request creation in collection menu

* fix: fixed issues with cURL paste for GraphQL requests in the URL input bar

* fix: added icons to create request dropdown

* fix: fixed the icon | text gap in dropdown

* fix: removed unnecessary updates on the Dropdown Component

* added onCreate to Dropdown to remove unwanted diffs

* fix: simplified the generateUniqueRequestName function. ai writes complex code

* chore: formatting and removed unnecessary diffs

* Update packages/bruno-app/src/components/RequestPane/QueryUrl/index.js

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

* chore: format

* Fix failing E2E tests by updating to new request creation flow

- Replace #create-new-tab selector with new dropdown flow using createUntitledRequest helper
- Update generateUniqueRequestName to handle .bru, .yml, and .yaml file extensions
- Add createUntitledRequest helper function with optional URL and tag parameters
- Update all failing tests to use the new helper function
- Fix selectors from .collection-item-name to .item-name where needed
- All 13 previously failing tests now pass

* chore: removed unused import

---------

Co-authored-by: Sid <siddharth@usebruno.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-09 19:08:52 +05:30
Anoop M D
03e8f2d67d Merge pull request #6303 from bpacholek/feature/windows-on-arm64-support
Feature: Enabled ARM64 build for Windows.
2025-12-09 17:14:17 +05:30
dependabot[bot]
8e855e53bf chore(deps): bump body-parser from 1.20.3 to 2.2.0 (#4383)
* chore(deps): bump body-parser from 1.20.3 to 2.2.0

Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.3 to 2.2.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.3...v2.2.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: parse raw body for content types not already handled by other parsers

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-09 12:19:06 +05:30
james-ha-bruno
599636d56b update for duplicative entry (#6356) 2025-12-09 12:17:10 +05:30
Anoop M D
9b9534c1eb feat: toolbar design updates (#6354)
* feat: toolbar design updates

* chore: addressed coderabbit review comments

* fix: update unit tests

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-09 12:15:18 +05:30
Abhishek S Lal
0197ae37c8 refactor: update AppTitleBar and SidebarHeader components (#6341)
* refactor: update AppTitleBar and SidebarHeader components to use MenuDropdown and ActionIcon for improved UI consistency

* refactor: update button locators in tests to use data-testid for consistency and improved readability
2025-12-08 22:06:24 +05:30
Dániel Seres
cf969dfcd6 fix: Support @contentType for multiline values (#6217)
* fix: Support @contentType for multiline values

Fixes the issue where the @contentType annotation broke the parsing of multiline values.

* chore: add dotall flag to fileExtractContentType

Not strictly needed since body:file uses single-line values in practice,
but doesn't hurt and matches what multipartExtractContentType does.

---------

Co-authored-by: Márk Dániel Seres <markdaniel.seres@tesco.com>
2025-12-08 18:39:25 +05:30
Pragadesh-45
a66be21523 feat: Enhance runCollectionFolder to support selected request ordering (#6320)
* Refactor `runCollectionFolder` action to accept `selectedRequestUids` for filtering and ordering requests.
* Update IPC handler to process `selectedRequestUids`, ensuring requests are executed in the specified order while preserving folder data.
2025-12-08 17:16:24 +05:30
naman-bruno
4016754d71 feat: integrate import/export modals and refactor environment handling (#6346) 2025-12-08 15:17:53 +05:30
Anoop M D
f3aebf6374 Merge pull request #6345 from usebruno/feat/design-updates
feat: design updates
2025-12-08 14:42:06 +05:30
naman-bruno
f87460b00e refactor: simplify last opened workspaces management by removing workspace config from storage and improving path handling (#6343) 2025-12-08 14:30:16 +05:30
naman-bruno
354e8d7496 feat: add hideApiSpecPage dispatch to Collection and CollectionItem components (#6344) 2025-12-08 14:24:47 +05:30
Anoop M D
dc107f8b96 init (#6337) 2025-12-08 01:37:16 +05:30
naman-bruno
cd0f1e45ba init 2025-12-07 21:53:47 +05:30
Bijin A B
33022843f2 fix: CWE-347: Improper Verification of Cryptographic Signature (#6336) 2025-12-07 14:16:39 +05:30
Anoop M D
facdf3264a feat: changes to incorporate oc schema updates (#6335)
* feat: changes to incorporate oc schema updates
* chore: fixed oc types resolution issue
2025-12-07 06:05:48 +05:30
naman-bruno
4ffb447c53 fix: path for newly added collection & remove option for outside collections (#6331)
* fixes

* fixes

* fix
2025-12-06 18:43:53 +05:30
Sanjai Kumar
3e5ae613f5 feat: Increase visibility of text in Request tabs (#6243)
* refactor(RequestTabs): update tab width calculation and improve styling

* refactor: replace close icon implementation with GradientCloseButton and adjust styles

* changes: design

* fix: failing tests

* fixes

* fixes: coderabbit

* fixes

* fixes

* gradient color fix

---------

Co-authored-by: naman-bruno <naman@usebruno.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-06 18:42:57 +05:30
naman-bruno
42bef4ae1e fix: traffic light styling on light mode (#6333)
* fix

* fixes
2025-12-06 18:16:37 +05:30
naman-bruno
e93e545b81 improve: tests (#6321)
* improve: tests

* fixes

* fixes
2025-12-06 15:36:58 +05:30
Abhishek S Lal
4a8d787f31 feat: Moved Workspace Selector to the Titlebar of the window. (#6319)
* refactor: update sidebar components and styles, replace TitleBar with SidebarHeader, and enhance collections search functionality

* refactor: improve event listener management in AppTitleBar and clean up SidebarHeader styles

* fix: ensure safe access to layout preferences in AppTitleBar and set default order in SidebarHeader

* refactor: centralize toTitleCase utility and remove redundant implementations in AppTitleBar and WorkspaceSelector

* feat: enhance accessibility and testing for sidebar and devtools toggle buttons in AppTitleBar

* chore: quick fix on a flaky test

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-12-06 02:07:05 +05:30
Bijin A B
f5211f6a08 Update quotes rule for string in CODING_STANDARDS.md (#6327) 2025-12-06 02:01:03 +05:30
Sanjai Kumar
57222d2500 feat: enhance collection settings with environment modals (#6242)
* feat: enhance collection settings with environment modals

* refactor: remove unused environment modal state and simplify Info component structure
2025-12-05 19:20:04 +05:30
Sanjai Kumar
f479e0d325 refactor: Rename runtime to runDuration (#6323)
* refactor: Rename 'runtime' to 'runDuration'

* revert changes made in report.html
2025-12-05 19:17:06 +05:30
naman-bruno
5302addda0 fix: clone collection (#6322)
* fix: clone collection
2025-12-05 17:26:06 +05:30
Sanjai Kumar
80b017f224 feat: Include pre-request and post-response tests in JUnit reports (#6284)
* enhance: JUnit output to include preRequest and postResponse test results

* fix: lint
2025-12-05 12:04:32 +05:30
Bijin A B
b18d582004 Merge pull request #6310 from sanjaikumar-bruno/chore/eslint-ignore-paths
chore: update ESLint configuration to ignore additional directories
2025-12-04 19:23:25 +05:30
Bijin A B
109394c65b Merge pull request #6308 from Pragadesh-45/fix/6254
feat: Streamline gRPC requests to use right context
2025-12-04 18:58:10 +05:30
Sid
c355153f26 Revert: Re-add post response vars (#6307)
* Partial Revert "remove: presets and response var (#6195)"

This reverts commit 786a3414b8 while keeping code related to presets deleted

* revert: remove global environment variables assignment
2025-12-04 18:04:47 +05:30
Pragadesh-45
b87a02beb3 feat: Streamline gRPC requests to use right context 2025-12-04 18:00:32 +05:45
sanjai
4624ffb116 chore: update ESLint configuration to ignore additional directories 2025-12-04 16:34:48 +05:30
Sid
a9ce97fb1b fix: update content security policy to remove unsafe-inline (#6305) 2025-12-04 12:40:52 +05:30
Pooja
72ce6cadeb fix: request and response pane height (#6294) 2025-12-04 12:31:57 +05:30
Bijin A B
c4ff2918a2 Merge pull request #6080 from dssagar93/feature/auto-scroll-on-tab-change
Auto scroll to show this item when its tab becomes active
2025-12-04 10:05:32 +05:30
Bijin A B
9972eb3de6 Merge branch 'main' of github.com:usebruno/bruno into feature/auto-scroll-on-tab-change 2025-12-04 05:15:39 +05:30
naman-bruno
ebe0203415 init: workspaces (#6264)
* init: workspaces
2025-12-04 04:56:43 +05:30
IDCT Bartosz Pachołek
b3ef91fe8e Enabled ARM64 build for Windows. 2025-12-03 23:57:15 +01:00
Bijin A B
6786f19d04 Merge pull request #6300 from usebruno/chore/oss-lint-fixes
chore: repo-wide lint fixes
2025-12-04 03:40:29 +05:30
Bijin A B
05fe8b1b27 chore: repo-wide lint fixes 2025-12-04 01:37:20 +05:30
sanish chirayath
b5722bf11c fix: wrap script in async IIFE to create isolated scope (#6229)
* fix: lexical scope for running scripts

* review fixes
2025-12-04 00:23:06 +05:30
Siddharth Gelera (reaper)
2b8da39bcf fix: update content security policy to allow inline scripts (#6139) 2025-12-03 19:57:10 +05:30
Pragadesh-45
38ba53be9f fix: stringify rawValue in brunoVarInfo (#6281) 2025-12-03 18:27:12 +05:30
Chirag Chandrashekhar
9159f523d9 Inbuilt Terminal (#6066)
* add: terminal

* added support for multiple terminal sessions and opening terminal from collection's working directory

* Use PowerShell as default shell on Windows

Replace cmd.exe with powershell.exe for terminal sessions on Windows.
Falls back to PWSH environment variable if set (for PowerShell Core).

* refactor(ui): improved session list by moving path display to hover for cleaner view

* chore: format

* refactor: improve terminal code quality and UI consistency

- Add terminal icon to 'Open in Terminal' dropdown item in CollectionItem
- Remove unused imports and functions (IconPlus, callIpc, canWriteToTerminal)
- Fix React key prop placement in SessionList component
- Replace deprecated substr with substring in terminal session ID generation
- Improve error handling for terminal cleanup on app quit
- Simplify terminal cleanup logic in window close handler

---------

Co-authored-by: naman-bruno <naman@usebruno.com>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-03 17:48:28 +05:30
Abhishek S Lal
a3d2d35d2e feat: introduce REQUEST_TYPES constant and update item deletion logic (#6244)
- Added REQUEST_TYPES constant to centralize request type definitions.
- Updated deleteItem action to filter items based on REQUEST_TYPES and folders.
- Modified collection.js to handle REQUEST_TYPES during file resequence operations.
2025-12-03 16:07:38 +05:30
Chirag Chandrashekhar
9caef9e573 Fix: WS variable interpolation (#6184)
* feat: add variable interpolation support for WebSocket requests

- Add WebSocket body interpolation in interpolateVars function
- Interpolate URL, headers, and all messages in request.body.ws array with full variable context
- Refactor sendWsRequest to use main process preparation (removes duplication)
- Add mode property to wsRequest object for proper request type detection
- Ensure consistent variable precedence matching HTTP/gRPC requests
- Centralize all interpolation logic in main process via prepareWsRequest

* Add Playwright tests for WebSocket variable interpolation

- Add tests for URL interpolation (wss://echo.{{url}}.org)
- Add tests for message content interpolation ({"test": "{{data}}"})
- Update test fixtures to use wss://echo.websocket.org echo server
- Add WEBSOCKET_FLOWS.md documentation
- Refactor queueWsMessage to handle variable interpolation in main process

* removed ws flow documentation

* chore: updated the network/index.js file to reduce merge conflicts by moving around code

* fix: added collection and item to WsQueryUrl Editor to fix available variable highlight

* chore: remove unnecessary whitespace in WebSocket event handlers

* feat: add automatic WebSocket reconnection on URL variable changes

- Detect changes to interpolated URL (including variable changes)
- Automatically disconnect and reconnect when interpolated URL changes
- Add debouncing (400ms) to prevent excessive reconnections
- Track previous interpolated URL to avoid unnecessary reconnects
- Store interpolated URL when connection becomes active
- Improve error handling and cleanup

* chore: removing diff

* Add WebSocket connection status IPC method

- Add connectionStatus() method to WsClient that returns detailed status
  ('disconnected', 'connecting', 'connected') instead of boolean
- Add renderer:ws:connection-status IPC handler in electron layer
- Add getWsConnectionStatus() utility function in network utils
- Provides more granular connection state information for UI components

* refactor: improve WebSocket connection status tracking in WsQueryUrl

- Replace boolean isConnectionActive with connectionStatus state ('disconnected', 'connecting', 'connected')
- Add useWsConnectionStatus hook to poll connection status every 2 seconds
- Refactor connection handlers: handleConnect, handleDisconnect, handleReconnect
- Update to use getWsConnectionStatus instead of isWsConnectionActive for more granular status
- Improve reconnect logic to handle URL variable interpolation changes
- Add proper connection status indicators in UI (connecting state with pulse animation)

* fix: improve WebSocket URL handling and request initialization

- Fix WebSocket URL state management by reading directly from item instead of local state
- Add handleUrlChange function to properly dispatch URL changes
- Fix interpolated URL change detection logic in useEffect
- Initialize params array for new WebSocket requests to prevent undefined errors
- Ensure params array is initialized when URL changes in draft/request
- Remove console.log statements and unused imports
- Update persistence test replacement URL to avoid port conflicts

These changes ensure WebSocket requests properly handle URL changes and
maintain consistent state between draft and saved requests.

* feat: refactor WebSocket connection status handling

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-03 15:52:52 +05:30
Siddharth Gelera (reaper)
893058067d chore: improve coderabbit review instructions (#6282)
* chore: add more review items

* Update CODING_STANDARDS.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Sid <siddharth@usebruno.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-03 13:39:24 +05:30
Anoop M D
4a38f2d49f Update PR template to consent for AI usage 2025-12-03 03:49:02 +05:30
Tarunkumar
d56e4f625b fixed min width issue so two windows can be used in parallel (#5863)
* fixed min width issue so two windows can be used in parallel

* updated the min height as well, so that Bruno can be viewed in quarter window on screen

---------

Co-authored-by: Tarunkumar N Bagmar <tarunkumar@Tarunkumars-MacBook-Air.local>
Co-authored-by: Anoop M D <anoop@usebruno.com>
2025-12-02 21:39:03 +05:30
Sid
9bbcf7ecbe fix:match to full string (#6272) 2025-12-02 17:54:53 +05:30
Chirag Chandrashekhar
ee4c923bc5 Merge pull request #6117 from chirag-bruno/bugfix/postman-export-omit-collection-vars
fix: Exporting Bruno collection as Postman collection omits collection variables
2025-12-02 16:47:15 +05:30
naman-bruno
bc82536a82 fix: ws and grpc request not saving (#6267) 2025-12-02 15:54:54 +05:30
Sid
b95ef99ef2 Merge pull request #6266 from usebruno/feat/copy-response-to-clipboard-5409
added copy button to copy response (#5409)
2025-12-02 15:47:18 +05:30
Pooja
2a251b1a62 added copy button to copy response (#6131)
* added copy button to copy response

* add: response action component

* fix: lint error

* add: playwright test

---------

Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-02 15:33:01 +05:30
Pooja
06a024a1d9 feat: add copy paste feature for folder (#6097)
* feat: add copy paste feature for folder

* add: playwright test

* fix

* fix

* add: keyboardFocusBg in light mode

* fix: copy paste in yaml collection

* improvement
2025-12-02 15:29:32 +05:30
Shashank Shekhar
8cee7bad39 added copy button to copy response (#5409)
Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
2025-12-02 14:53:51 +05:30
Chirag Chandrashekhar
bc4062b950 Bugfix/inaccurate process metrics (#6257) 2025-12-02 14:40:20 +05:30
Sid
b3ffc904ad feature/autoSave (#582) (#6211) 2025-12-02 14:32:34 +05:30
Sid
af707de684 fix(autoSave): minor bug fixes 2025-12-02 14:09:19 +05:30
Sid
be94224cfd Merge branch 'main' into feat/autosave-internal-582 2025-12-02 13:53:46 +05:30
Pooja
786a3414b8 remove: presets and response var (#6195) 2025-12-02 13:53:04 +05:30
Sid
7de56bd85c Merge branch 'main' into feat/autosave-internal-582 2025-12-02 13:52:12 +05:30
Pooja
4ce5debc4c feature/autoSave (#582) (#6225) 2025-12-02 13:46:06 +05:30
Pooja
8716e2b2a6 change: default cli sandbox to safe (#6198)
* change: default cli sandbox to safe
2025-12-02 13:09:16 +05:30
Sanjai Kumar
9d6486ba3e fix: update OAuth2 authorization header encoding to remove unnecessary URI encoding (#6263) 2025-12-02 12:56:08 +05:30
tlaloc911
dd72ee5d77 Add package directories to .gitignore (fix #6249) (#6250) 2025-12-01 09:23:44 +05:30
dependabot[bot]
7f204a8769 chore(deps): bump actions/upload-artifact from 4 to 5 (#5912)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-29 00:21:24 +05:30
dependabot[bot]
32990db3fb chore(deps): bump actions/checkout from 5 to 6 (#6192)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-29 00:18:20 +05:30
lohit
4f8d2c0c67 feat: default developer mode to nodevm and remove vm2 (#6187) 2025-11-29 00:04:55 +05:30
Anoop M D
8c06a229e9 Merge pull request #6238 from usebruno/chore/add-codemirror-placeholder
chore: added codemirror placeholder
2025-11-28 06:07:18 +05:30
Anoop M D
f367ea5a89 chore: added codemirror placeholder 2025-11-28 06:06:22 +05:30
Anoop M D
55a6af1ce3 Merge pull request #6236 from naman-bruno/bugfix/dropdown
rm: api spec from collection dropdown
2025-11-28 05:59:23 +05:30
Anoop M D
e9efcb48ac Merge pull request #6237 from usebruno/feat/design-revamp
feat: design revamp
2025-11-28 05:39:14 +05:30
Anoop M D
fa94efaa24 feat: design revamp 2025-11-28 05:10:29 +05:30
naman-bruno
1b2df9fba4 fix: dropdown 2025-11-28 02:09:48 +05:30
naman-bruno
7ee366eb81 Redesign dropdowns (#6235)
* redesign dropdowns

* fix: colors

---------

Co-authored-by: Anoop M D <anoop@usebruno.com>
2025-11-27 22:20:51 +05:30
Abhishek S Lal
59514127d5 Merge pull request #6171 from abhishek-bruno/style/update-font-size
style: updated font size to 13px using theme props.
2025-11-27 22:19:35 +05:30
Sanjai Kumar
9d98eb86c4 refactor: update deprecation messages for Presets and Post Response Vars (#6230)
* refactor: update DeprecationWarning component to accept children and enhance deprecation messages for Presets and Post Response Vars

* refactor: update DeprecationWarning component to use props for feature names and links, enhancing deprecation messages across various components
2025-11-27 21:57:39 +05:30
Sanjai Kumar
bb0096eb38 feat: added multipart data formatting in timeline (#6185)
refactor: remove escapeHeaderValue function and enhance formatMultipartData utility
2025-11-27 18:12:20 +05:30
Sanjai Kumar
6e88671788 feat: add support for legacy request object translations in Postman converter (#6174) 2025-11-27 10:42:06 +05:30
naman-bruno
d17048f80c feat(opencollection): add YAML-based collection support (#6155)
* add: opencollection

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-11-27 00:55:08 +05:30
Sanjai Kumar
172479edad feat: Add deprecation warnings for Presets and Post Response Vars (#6212)
* feat: add deprecation warnings for presets and post response vars

* refactor: update deprecation warnings for presets and post response vars to include version
2025-11-26 21:14:50 +05:30
sanish chirayath
486b91894c fix: fetching reflection adds draft state in gRPC (#6218) 2025-11-26 19:51:34 +05:30
sanish chirayath
ca8ef36f9f fix: grpc messages vanishes after save if the body contains variables (#6216) 2025-11-26 18:50:10 +05:30
Chirag Chandrashekhar
7ed474c8ba fix: add Error constructors to NodeVM context for jsonwebtoken tests (#6209)
When jsonwebtoken throws errors inside the NodeVM context, those errors
were instances of the VM's isolated Error class, which caused
instanceOf(Error) checks in tests to fail.

By adding Error constructors (Error, TypeError, ReferenceError,
SyntaxError, RangeError) from the global scope to the scriptContext,
errors thrown by jsonwebtoken and other modules now use the same Error
class that tests check against, ensuring instanceOf checks pass correctly.

This fixes jsonwebtoken test failures when using the NodeVM runtime.
2025-11-26 17:57:11 +05:30
srikary12
086d0d98ef feature/autoSave (#582) 2025-11-26 12:17:52 +05:30
Pooja
b0405b1e1a fix: variable name validation in brunoVarInfo (#6203)
* fix: variable name validation in brunoVarInfo
2025-11-26 00:50:57 +05:30
Bijin A B
c2d000e805 fix: disallow prompts with leading or trailing spaces (#6201) 2025-11-25 16:53:05 +05:30
sanish chirayath
6aaccabc04 fix: openapi import fails when requestbody content is empty (#6200) 2025-11-25 16:19:57 +05:30
Sid
daf23c9e2d feat: add coding standards documentation (#6141) 2025-11-25 13:30:26 +05:30
Pooja
f952688032 improve: add var functions (#6175)
* improve: add var functions

* Update packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-11-21 21:12:06 +05:30
Bijin A B
f429fa94e3 fix(security): prototype pollution vulnerability in js-yaml (#6168) 2025-11-21 17:42:31 +05:30
Pooja
fb420fcea4 fix: preserve draft state when creating variables via varInfoToolbar (#6152)
* fix: preserve draft state when creating variables via varInfoToolbar
2025-11-21 15:47:23 +05:30
Anoop M D
cc3d6a961a Merge pull request #6169 from pooja-bruno/fix/reduce-font-size-of-tabs-text
fix: reduce font size of tab test
2025-11-21 13:41:31 +05:30
pooja-bruno
27c37192b2 fix: reduce font size of tab test 2025-11-21 12:59:27 +05:30
Bijin A B
faa2ef5de2 Merge pull request #6162 from bijin-bruno/fix/bruno-to-postman-converter
fix: sync bruno to postman converter with enterprise edition
2025-11-20 19:13:33 +05:30
Sanjai Kumar
c05d56fd21 Improve "Close All Collections" community PR (#5994)
* refactor: move CollectionsBadge to a dedicaced folder

Co-authored-by: Jérémy Munsch <github@jeremydev.ovh>
2025-11-19 22:48:30 +05:30
Pragadesh-45
b4d19ab8ca fix: push event only if exec has content (#6121) 2025-11-19 12:09:18 +05:30
Siddharth Gelera (reaper)
0cedf48e68 feat: encapsulate tab boundaries into a hook for managing pane dimensions (#5878)
* feat: implement useTabPaneBoundaries hook for managing pane dimensions

* fix: replace hardcoded divisor with constant in useTabPaneBoundaries

* chore: un-needed event calls

* fix: remove redundant import of sendRequest

* update main rediff
2025-11-19 11:30:39 +05:30
Pooja
4e7bc1a351 fix: prevent import failure for Postman collections with missing response headers (#6129) 2025-11-19 07:53:18 +05:30
Sid
9d3c8b2401 feat: Allow ctrl/cmd + click to open URLs present in codemirror (#5930)
* feat: Allow ctrl/cmd + click to open URLs present in codemirror editors (#5160)

* Allow ctrl/cmd + click to open URLs

* fix for when user does cmd+tab, then comes back without it

---------

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

* Feature/cmd click on links (#5927)

fix: clean up whitespace and formatting in linkAware functions

fix rediff

Feature/cmd click on links (#6132)

* Allow ctrl/cmd + click to open URLs

* fix for when user does cmd+tab, then comes back without it

* refactored the community contribution to match Autocomplete's implementation

* updated the code to resolve issues caused during merge conflict resolution with the use of makeLinkAware

* fix: updated the code to use lodash's debounce and removed redundant undefined checks

* fix: correct debouncing test expectation in linkAware.spec.js

The test was incorrectly expecting 3 setTimeout calls when debouncing
should only result in one active timeout. Updated the test to verify
debouncing behavior correctly by checking that setTimeout is called
with the correct delay, and that only one execution happens after
the debounce delay.

* fix: fixed merge issues in linkAware.js

* fix: fixed CodeMirror assignment to this.editor

* fix: formatting fixes

* fix: formatting fix

---------

Co-authored-by: abansal21 <37215457+abansal21@users.noreply.github.com>
Co-authored-by: Chirag Chandrashekhar <chirag@usebruno.com>

---------

Co-authored-by: Arun Bansal <37215457+abansal21@users.noreply.github.com>
Co-authored-by: Chirag Chandrashekhar <chirag@usebruno.com>
2025-11-18 17:56:37 +05:30
Sid
39dfd8d360 Feature/cmd click on links (#5927)
fix: clean up whitespace and formatting in linkAware functions

fix rediff

Feature/cmd click on links (#6132)

* Allow ctrl/cmd + click to open URLs

* fix for when user does cmd+tab, then comes back without it

* refactored the community contribution to match Autocomplete's implementation

* updated the code to resolve issues caused during merge conflict resolution with the use of makeLinkAware

* fix: updated the code to use lodash's debounce and removed redundant undefined checks

* fix: correct debouncing test expectation in linkAware.spec.js

The test was incorrectly expecting 3 setTimeout calls when debouncing
should only result in one active timeout. Updated the test to verify
debouncing behavior correctly by checking that setTimeout is called
with the correct delay, and that only one execution happens after
the debounce delay.

* fix: fixed merge issues in linkAware.js

* fix: fixed CodeMirror assignment to this.editor

* fix: formatting fixes

* fix: formatting fix

---------

Co-authored-by: abansal21 <37215457+abansal21@users.noreply.github.com>
Co-authored-by: Chirag Chandrashekhar <chirag@usebruno.com>
2025-11-18 17:44:24 +05:30
Arun Bansal
460832f3ed feat: Allow ctrl/cmd + click to open URLs present in codemirror editors (#5160)
* Allow ctrl/cmd + click to open URLs

* fix for when user does cmd+tab, then comes back without it

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-11-18 17:43:56 +05:30
Sanjai Kumar
50442d960d feat: enhance HTML report generation by including environment name (#6055) 2025-11-18 16:09:57 +05:30
Abhishek S Lal
2ac41806a2 fix: update result structure to use 'name' instead of 'suitename' in JUnit output (#6120)
* fix: update result structure to use 'name' instead of 'suitename' in JUnit output
2025-11-18 12:31:41 +05:30
Bijin A B
e9111c0529 Merge pull request #6104 from bijin-bruno/feature/prompt-vars-extended 2025-11-17 22:02:11 +05:30
Bijin Bruno
48a09f6f50 feat: enhance support for prompt variables 2025-11-17 20:12:20 +05:30
Bijin A B
e613e4cbcd Merge pull request #5975 from abhishek-bruno/fix/reorder-item-when-deleting-v2
Refactor: Enhance Request Item sequencing
2025-11-17 16:19:00 +05:30
Pooja
4631eda281 Merge pull request #6069 from pooja-bruno/feat/add-edit-variable-in-place
feat: edit variable in place
2025-11-17 16:13:09 +05:30
Abhishek S Lal
3f7ab31b2b refactor: enhance deleteItem action to handle item reordering after deletion 2025-11-17 16:05:33 +05:30
Bijin A B
27a7b623c7 Merge pull request #6039 from sanish-bruno/feat/openapi-examples
fix: import multiple types of example formats from openapi
2025-11-17 14:12:35 +05:30
sanish-bruno
95bc670d8c fix: regex 2025-11-17 13:53:24 +05:30
sanish-bruno
6d8f428140 refactor 2025-11-17 13:53:24 +05:30
sanish-bruno
ed18cb6d90 fix: improve logic for and tests 2025-11-17 13:53:24 +05:30
sanish-bruno
bb83fbfb9d fix: add schema based example 2025-11-17 13:53:24 +05:30
Sid
ddfdeda4d6 Merge pull request #6074 from usebruno/feature/http-stream-internal
Feature: HTTP Streaming
2025-11-17 13:44:53 +05:30
skewnart
adb0b90457 fix: reorder request and directory when deleting item 2025-11-17 13:40:13 +05:30
Pooja
8c7888533a feat: support newlines in headers, params, and variables (#5795)
* feat: support newlines in headers, params, and variables

* add: collectin unit test

* fix: assertion and additional header multiline

* fix: assert

* rm: useEffect for header validation

* rm: comments

* fix: already encoded url

* rm: new line changes

* handle new line in url

* fix: lint error

* add: unit test for multi line test

* change: unit test

* mv: functions in util

* fix: drag icon position

* improve: arrow height

* improvements

* rm: getKeyString from assert

* fix: single line editor

* fix: import MultiLineEditor

* import getKeyString and getValueUrl

* add: getTableCell in utils

* rm: multiline key logic

* fix

* mv: getTableCell in locators.ts
2025-11-17 13:27:00 +05:30
Sid
0a188575a0 fix: update request cancel icon 2025-11-17 13:14:09 +05:30
Chirag Chandrashekhar
2be602d16c Feature/prompt save before collection close (#6062)
* added confirmation dialog before collection close for items in draft state

* chore: lint fix

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-11-17 12:09:12 +05:30
Chirag Chandrashekhar
8ec1925b9f feat: add variable interpolation support for WebSocket requests (#6064)
* feat: add variable interpolation support for WebSocket requests

- Add WebSocket body interpolation in interpolateVars function
- Interpolate URL, headers, and all messages in request.body.ws array with full variable context
- Refactor sendWsRequest to use main process preparation (removes duplication)
- Add mode property to wsRequest object for proper request type detection
- Ensure consistent variable precedence matching HTTP/gRPC requests
- Centralize all interpolation logic in main process via prepareWsRequest

* Add Playwright tests for WebSocket variable interpolation

- Add tests for URL interpolation (wss://echo.{{url}}.org)
- Add tests for message content interpolation ({"test": "{{data}}"})
- Update test fixtures to use wss://echo.websocket.org echo server
- Add WEBSOCKET_FLOWS.md documentation
- Refactor queueWsMessage to handle variable interpolation in main process

* removed ws flow documentation

* chore: updated the network/index.js file to reduce merge conflicts by moving around code

* fix: added collection and item to WsQueryUrl Editor to fix available variable highlight

* chore: remove unnecessary whitespace in WebSocket event handlers

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-11-17 12:02:25 +05:30
Bobby Bonestell
d28f2f32e9 feat: add support for prompt variables in the bruno app 2025-11-14 18:57:45 +05:30
Sid
76a1532695 Merge branch 'main' into feature/http-stream-internal 2025-11-14 17:01:09 +05:30
Siddharth Gelera (reaper)
efad149afc HTTP stream enhancements (#6077)
* feat: add stop request button in api url bar

* docs: add farsi translation

* fix: handle escaped forward slashes by fast-json-format library upgrade

* refactor: change ui to use one from Websockets

* chore: cleanup

* fix: lint issues

* Replace IconPlayerStop with IconSquareRoundedX

* update json request and response formatting logic

* chore: format changes

* chore: remove un-needed diffs

* chore: sanitize

* bugfix(#5939): curl import fails for custom content-types

* chore: remove un-needed diffs

* chore: enhance response handling for streaming

* fix: disable requestid check for tests and assertions to be updated after streaming result

* chore: housekeeping

* fix: streamline loading and cancel request icon logic

* chore: formatting

* fix: multiple co-pilot changes

* fix: handle in folders

* feat: add WaitGroup utility for managing concurrent tasks

* refactor: remove WaitGroup utility and clean up network IPC logic

* refactor: remove unused setTimeout import and clean up post script execution

* refactor: clean up post-response script execution logic

* undiff

* re-align

* refactor: streamline post-response script execution

- Cleaned up formatting and improved readability of the post-response script execution logic.
- Consolidated parameters in function calls for consistency.

* fix: keep original dataBuffer for saving response

---------

Co-authored-by: adarshajit <adarshajit@gmail.com>
Co-authored-by: sajadoncode <sajadoncode@gmail.com>
Co-authored-by: lohit-bruno <lohit@usebruno.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
Co-authored-by: Anoop M D <anoop@usebruno.com>
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
Co-authored-by: Dawid Góra <dawidgora@icloud.com>
2025-11-14 16:57:29 +05:30
Sid
2d2a17c90f Merge pull request #6070 from usebruno/feature/collection-test-results-and-filtering-internal
Feature/collection test results and filtering internal
2025-11-14 13:46:32 +05:30
Sid
3d8d93f20d fix: add missing newline at end of file in RunnerResults component 2025-11-14 13:19:39 +05:30
Bijin A B
94c33e6833 Merge pull request #5653 from sanish-bruno/feat/support-grpc-v1-reflection
feat: support v1 reflection for grpc server reflection
2025-11-13 20:12:26 +05:30
SAGAR KHATRI
f7ea1f8dbb Added semi colon 2025-11-13 19:31:21 +05:30
sanish-bruno
2ef451c80b rm: adding metadata to credentials 2025-11-13 19:04:34 +05:30
sanish-bruno
044fcce49f fix 2025-11-13 19:04:34 +05:30
sanish-bruno
dffb600dab fix: set metadata 2025-11-13 19:04:34 +05:30
sanish-bruno
99478b7068 fix: add metadata for insecure methods calls 2025-11-13 19:04:34 +05:30
sanish-bruno
252fd386b7 add: metadata to creds 2025-11-13 19:04:33 +05:30
sanish-bruno
b982f6db16 refactor: replace grpc-reflection-js with grpc-js-reflection-client in grpc-client implementation
rm: comment

fix: type generation

feat: implement reflection client support for gRPC v1 and v1alpha in grpc-client

refactor: simplify reflection client handling in grpc-client by removing service list retrieval

refactor: enhance reflection client return structure in grpc-client to include service list

fix: lint
2025-11-13 19:04:33 +05:30
Bijin A B
3b4e5686b8 Merge pull request #5800 from sanish-bruno/add/grpc-make-request-tests
add: tests for grpc requests
2025-11-13 18:36:11 +05:30
Bijin Bruno
2ef1a1948b chore: minor folder structure refactor 2025-11-13 18:23:34 +05:30
sanish-bruno
f2273821b0 add: tests for grpc requests
feat: add common selectors to locator.ts

fix: add dataTestId prop

update locator
2025-11-13 18:10:30 +05:30
Bijin A B
8a22f6acb8 Merge pull request #6083 from dawidgora/bugfix/5939_curl-import-fails-for-custom-content-types
bugfix(#5939): curl import fails for custom content-types
2025-11-13 17:26:49 +05:30
Chirag Chandrashekhar
6049530634 refactor: update runner tests to use new filter implementation and reusable helpers (#6085) 2025-11-13 15:56:33 +05:30
Dawid Góra
5784b04129 bugfix(#5939): curl import fails for custom content-types 2025-11-13 08:50:29 +01:00
Anoop M D
fec37f43e0 Merge pull request #5993 from adarshajit/feature/stop-request-button-in-api-url-bar
feat: add stop request button in api url bar
2025-11-13 12:14:48 +05:30
SAGAR KHATRI
cf19035b0b Merge branch 'usebruno:main' into feature/auto-scroll-on-tab-change 2025-11-12 23:53:05 +05:30
Bijin A B
b8fef7b796 Merge pull request #6079 from lohit-bruno/json_response_formatting
fix: update json request and response formatting logic
2025-11-12 23:12:40 +05:30
lohit-bruno
04f8dba1b1 update json request and response formatting logic 2025-11-12 22:47:35 +05:30
SAGAR KHATRI
d9a3f74cb7 Auto scroll to show this item when its tab becomes active 2025-11-12 20:50:44 +05:30
Anoop M D
cd1500bd01 Replace IconPlayerStop with IconSquareRoundedX 2025-11-12 19:48:09 +05:30
Anoop M D
e8a8b5d220 Merge pull request #6027 from sajadoncode/feature/add-farsi-readme
docs: add farsi translation
2025-11-12 19:04:11 +05:30
Pragadesh-45
bc3dfc59f6 fix: lint issues 2025-11-12 18:35:14 +05:45
Bijin A B
2c399ca33c Merge pull request #6075 from lohit-bruno/upgrade_fast_json_format_library
fix: handle `escaped forward slashes` by `fast-json-format` library upgrade
2025-11-12 18:06:09 +05:30
lohit-bruno
ccac4d6112 fix: handle escaped forward slashes by fast-json-format library upgrade 2025-11-12 16:38:35 +05:30
DaviXavier
fc5093eab4 fix: #1884 - Fixes infinite loading issue for text/event-stream requests (#4472)
* #1884 - Add support for text/event-stream content-type

* #1884 - Fix bugs with streaming

Fix bug when streaming response is not ok
Fix bug when clearing response of streaming request
Show text signaling that the response is being streamed in the reponse status
Update response size when new data is streamed in

* #1884 - Fix multiple requests when spamming send button

* #1884 - Add time counter for streamed response and fix final time

* #1884 - Run post script only at end of streamed request

* #1884 - add support for automatic "upgrade" to streaming data

* #1884 - adjustments for stopwatch in stream implementation and remove unused imports

* #1884 - fix imports indentation in useIpcEvents.js

* #1884 - remove stream data ended export function from collections

---------

Co-authored-by: Siddharth Gelera <ahoy@barelyhuman.dev>
2025-11-12 15:56:26 +05:30
Bijin A B
631b05330d Merge pull request #6071 from barelyhuman/fix/restore-duplicate-hasher
test: Add test for restoring duplicate hashes in patternHasher
2025-11-12 15:45:17 +05:30
reaper
be34c86c47 fix: replace regex with replaceAll for secure string replace 2025-11-12 15:12:51 +05:30
reaper
67c9f1373e test: Add test for restoring duplicate hashes in patternHasher 2025-11-12 14:40:30 +05:30
Sid
6628f95677 fix: add missing newline at end of file in RunnerResults component 2025-11-12 14:15:06 +05:30
Chirag Chandrashekhar
44ed0b01d8 Test Runner UI Revamp (#6011)
* Moved collection results to runner title bar so they are move visible.
Added breakdown of test results within collection.
Added filtering based on passing/failing requests and tests by click on results text.

* feat: revamp Test Runner UI with unified filter and improved layout

- Add unified filter bar (All/Passed/Failed/Skipped) with counts and active indicator
- Implement filtering that filters both requests and tests within requests
- Move action buttons to top bar, prevent filter wrapping
- Add close button and placeholder to response view
- Update styling for light/dark modes with proper colors and typography

* refactored the RunnerResults component to be more clear and readable

* refactor: revert formatting changes while preserving new UI and filtering logic

- Restore original function formatting with return statements and braces
- Restore removed input attributes (autoCorrect, autoCapitalize, spellCheck)
- Revert ternary operator changes to match original code style
- Restore original variable names (savedConfiguration) and comments
- Restore original test results rendering structure
- Preserve new filter bar UI, filtering logic, and response view improvements

* fix: implement smart auto-scroll behavior in test runner

- Only auto-scroll when user is near the bottom (within 100px)
- Preserve user's scroll position if they've scrolled up to view content
- Move ref to actual scrollable container for proper scroll detection

* Update RunnerResults component

* chore: reformat

---------

Co-authored-by: Morgan English <morgan.english@canterbury.ac.nz>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-11-12 14:10:36 +05:30
morgan-se
45cfbc5c49 Moved collection results to runner title bar so they are move visible. (#3808)
Added breakdown of test results within collection.
Added filtering based on passing/failing requests and tests by click on results text.

Co-authored-by: Morgan English <morgan.english@canterbury.ac.nz>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-11-12 13:48:31 +05:30
Pooja
14bece8696 feat: Add tabs component for pre-request and post-response scripts (#5926) 2025-11-12 12:53:32 +05:30
Pooja
9e19244665 move: import setting into import collection modal (#5929) 2025-11-12 11:36:21 +05:30
Pooja
f439f2de9a add: draft for collection and folder settings (#5947) 2025-11-12 11:11:12 +05:30
Bijin A B
e844d35b03 Merge pull request #6051 from abhishek-bruno/fix/postman-export-missing-tests
fix: modify brunoToPostman function to include tests in event section
2025-11-11 14:22:12 +05:30
Abhishek S Lal
26e140aca0 feat(converters): add test scripts in bruno to postman export 2025-11-11 12:56:45 +05:30
Bijin A B
7bd6a9a915 Merge pull request #6054 from barelyhuman/fix/regex-index
fix: remove global flag from TEMPLATE_VAR_PATTERN to avoid falsey matches
2025-11-10 19:25:17 +05:30
Siddharth Gelera
e1045372d5 fix: reset TEMPLATE_VAR_PATTERN lastIndex in URL validatio 2025-11-10 19:24:14 +05:30
Siddharth Gelera
914b858024 fix: update TEMPLATE_VAR_PATTERN to remove global flag 2025-11-10 18:15:57 +05:30
Siddharth Gelera
36e9a9c137 fix: reset TEMPLATE_VAR_PATTERN lastIndex in URL validation 2025-11-10 18:14:00 +05:30
Bijin A B
995899dedb Merge pull request #6053 from lohit-bruno/upgrade_fast_json_format_library
chore: `fast-json-format` library upgrade
2025-11-10 17:35:18 +05:30
lohit-bruno
408dd6bccf chore: fast-json-format library upgrade 2025-11-10 16:58:14 +05:30
Anoop M D
ab7ead91d5 chore: remove references to Golden Edition from multiple language readme files 2025-11-09 17:43:34 +05:30
Abhishek S Lal
a186df3ac4 refactor: update UI interactions and improve test stability (#6042) 2025-11-08 01:54:50 +05:30
Abhishek S Lal
3fe5299d8e fix: httpbun dependencies removed (#6041)
* fix: standardize URL formatting in insomnia test files

* feat: add mix router for handling custom redirects and cookies

* fix: add validation for redirect count to prevent infinite loops

* fix: update test URLs to use local server and add query parameters for improved testing
2025-11-07 21:55:33 +05:30
lohit
57c08350b6 fix: handle prettifying json data with bruno variables (#6038) 2025-11-07 21:07:30 +05:30
Siddharth Gelera (reaper)
68b2625259 feat(common): add patternHasher utility for hashing and restoring string from special characters (#6032)
* feat: add patternHasher utility for variable hashing

This utility function hashes input strings containing variables and allows for restoration of the original string. It includes support for custom matchers and handles cases where no variables are matched.

* Update packages/bruno-common/src/utils/template-hasher.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Sid <siddharth@usebruno.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-07 19:09:55 +05:30
Dann
1719cee440 fix: request URL input field overflow issue (#6020) 2025-11-07 16:27:11 +05:30
lohit
ab4e9eb3bd fix: use fast-json-format for prettifying json (#6026) 2025-11-07 16:20:12 +05:30
sajadoncode
b15c421270 docs: add farsi translation 2025-11-07 09:36:09 +01:00
Siddharth Gelera (reaper)
24a36bc355 fix: ensure protocolVersion is a number in WebSocket options (#6013) 2025-11-06 19:05:35 +05:30
lohit
1ac128e35c fix(bru-2079): validations for global environments get/set functions (#6009) 2025-11-06 18:12:39 +05:30
sanish chirayath
4510cc3414 fix: assert value input is disabled (#6012) 2025-11-06 17:22:27 +05:30
sanish chirayath
f3cb0d4bae Fix/example naming (#6002)
* fix: example naming

* fix: request not being saved when initialized with empty url

* remove check for method

* fix: improve the logic for get initial name

* fix test
2025-11-05 22:56:03 +05:30
lohit
7b183887ce fix(bru-2096): handle options prop for req body apis in safe mode (#6001) 2025-11-05 20:41:01 +05:30
sanish chirayath
fa28ab9b50 fix: response is unable to be copied (#5995)
* fix: response is unable to be copied

* fix: enable selection of text on readonly

* fix: no cursor when readonly
2025-11-05 20:17:09 +05:30
Abhishek S Lal
60b437ef9d fix: update test URLs having httpbin. Add redirect chain endpoint to test server (#5989)
* fix: update test URLs from httpbin to echo.usebruno.com across multiple test files

* fix: standardize URL formatting in insomnia test files

* chore: standardize URL formatting in insomnia test files
2025-11-05 20:16:07 +05:30
sanish chirayath
1ef8852a01 fix: grpc timeline crash (#5999) 2025-11-05 20:14:11 +05:30
Siddharth Gelera (reaper)
17cc70f36e fix: improve URL validation in GenerateCodeItem (#5998)
* fix: improve URL validation in GenerateCodeItem

Replaced direct URL validation with a new function to check for valid URLs and missing interpolations.

* Update packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 20:11:00 +05:30
DrChiodo
2deee11718 fix: restore text selection and copy in read-only CodeEditor (#5983)
Fixes #5982

Changed CodeMirror readOnly option from 'nocursor' to boolean true
to allow text selection while maintaining read-only behavior.
Removed CSS rules that prevented text selection (user-select: none)
in read-only mode.

This restores the ability to copy text from the Response panel using
Ctrl+C or right-click context menu, which was broken in nightly builds
after v2.13.2.
2025-11-05 19:17:20 +05:30
sanish chirayath
bdc8f391b7 Feat/response examples testing fixes (#5974)
* fix: cloning

feat: add response saving limit

fix: status code stays OK when i save an example

* fix: lint

* fix: newly created examples are not opening in new tab

* fix: playwright tests

* fix: use itemUId to fins item

* fix: response save
2025-11-05 19:13:21 +05:30
adarshajit
1656e951fb feat: add stop request button in api url bar 2025-11-05 17:08:19 +05:30
sanish chirayath
d8adb59d04 chore: rename crashing (#5985) 2025-11-05 14:13:49 +05:30
lohit
de05fb6137 fix(bru-1142): import environment functionality validations and fixes (#5964) 2025-11-04 18:05:25 +05:30
Kyle Bloom
3e3884a6af chore: Minimal fix for start time (#5961) 2025-11-04 18:01:50 +05:30
Bijin A B
23843bb621 fix: flaky large response test and update app preferences for few tests (#5963)
* fix: flaky large response test and update app preferences for few tests

* refactor: update createCollection function to accept sandbox mode
2025-11-02 11:35:09 +05:30
Bijin A B
c85a1ec1a5 chore: refactor few flaky tests (#5958) 2025-11-01 08:05:53 +05:30
sanish chirayath
68cbb7d9df Feat/add import export support for examples (#5936)
* feat: enhance Bru grammar to support response blocks and examples

- Added new grammar rules for response headers, status, and body types (JSON, XML, text).
- Introduced parsing logic for example blocks, allowing multiple examples with various body types.
- Implemented tests for example parsing, including edge cases and complex examples with authentication.
- Created fixture files for simple and complex examples to validate parsing functionality.

feat: extend jsonToBru functionality to support response handling and examples

- Updated jsonToBru to include parsing for response headers, status, and body types (JSON, XML, text).
- Enhanced example handling to support multiple examples with various body types.
- Added comprehensive tests for example parsing, including edge cases and complex scenarios with authentication.
- Created fixture files for testing the new features and validating parsing functionality.

move: files to fixtures folder

refactor: simplify response body handling in Bru grammar and JSON conversion

- Removed specific body type handling (JSON, XML, text) from grammar and semantics.
- Updated response body parsing in jsonToBru to handle a unified response body format.
- Adjusted tests and fixtures to reflect changes in response body structure, ensuring compatibility with the new format.

feat: add response bookmarking functionality to ResponsePane

- Introduced ResponseBookmark component to allow users to save responses as examples.
- Added NameExampleModal for naming saved examples.
- Updated ResponsePane to include the new bookmarking feature.
- Implemented Redux actions to manage response examples in the collections state.
- Enhanced CollectionItem to display saved examples and allow for expansion.

fix: remove unnecessary padding from ExampleItem component

feat: implement delete and rename functionality for examples in ExampleItem component

- Added DeleteExampleModal for confirming deletion of examples.
- Integrated modal for renaming examples with state management.
- Enhanced ExampleItem to handle example deletion and renaming through modals.
- Updated Redux actions to support example updates and deletions in the collections state.

fix: example writing to  disc properly

fix: example parsing errors

fix: request with example parsing error

fix: handle examples in collections and requests

feat: implement response example functionality in the application

- Added ResponseExample component to handle displaying and editing response examples.
- Integrated ResponseExampleRequestPane and ResponseExampleResponsePane for structured request and response handling.
- Enhanced RequestTabPanel and RequestTab components to support response-example tabs.
- Introduced new styled components for better UI/UX in response examples.
- Updated theme files to include styles for response examples.
- Implemented URL bar for editing request URLs in response examples.
- Added functionality for managing headers and parameters in response examples.
- Improved overall structure and organization of response example components.

add styles for example url bar

feat: add Checkbox component and Table-v2 for enhanced UI

- Introduced a new Checkbox component for better user interaction in forms.
- Added Table-v2 component to improve table rendering and resizing functionality.
- Updated existing components to utilize the new Checkbox and Table-v2 for managing headers and parameters in response examples.
- Enhanced styling for better visual consistency across components.
- Updated theme files to include styles for the new components.

feat: implement custom scrollbar styles for response example components

fix: features

add actions , view more

feat: enhance response example functionality

- Added GenerateCodeItem component for generating code snippets from response examples.
- Integrated modal for code generation within ResponseExample component.
- Updated ResponseExampleTopBar to handle example name and description editing.
- Improved state management for response examples, including new actions for updating names and descriptions.
- Enhanced ResponseExampleRequestPane to support editing and saving request details.
- Refactored URL handling in ResponseExampleUrlBar to utilize example-specific data.
- Improved overall user experience with better UI elements and state management.

feat: enhance response example management and UI components

feat: enhance editing capabilities in response example components

feat: update multipart form parameter handling in response examples

feat: refactor response example parameter handling and enhance UI interactions

feat: introduce RadioButton component and update Checkbox usage in response examples

fix: styles

fix radio button styling

fixed radio button styles

feat: add create example from sidebar

feat: enhance ResponseExample components with layout adjustments and new HeightBoundContainer

feat: add Checkbox and RadioButton components with comprehensive tests for rendering, user interactions, and accessibility

feat: playwright test csaes

rm: comments

fix: linting

fix: tests

refactor: update response example tests and enhance functionality

fix: tests

fix: e2e-tests

refactor: implement hasRequestChanges utility for better change detection

rm: console

rm: consoles

fix: lint

fix: tests

fix: response header disabled by default issue

Feat/with bru example parser (#5892)

* fix: response header disabled by default issue

feat: new parsing logic

fix: change test cases to accomodate new brulang

add: path params features

rm:consoles

six: make tab permanent on double click

fix width

feat: add status editing

feat: review fixes

review fixes

fix: review fixes

fix: post review

mv: test files

fix: review

* fix: lint

* fix: review comments

* fix: icons folder strcuture

fix: tests

fix: lint

fix: unit tests

feat: body mode selector

fix: close all collections

rm: example

feat added tests. lang change

feat: add custom status text

fix: status update

feat: add body mode, update tests

add default name prefilled for example

fix: active tab styles, prefilled name, text fixes

fix : pkg lock

fix: review

fix: review comments

fix: hide cursor when readonly

fix: height

fix: null body

fix: response body parsing

fix: test cases

feat: add method support for examples

fix: reponse parsing

fix: update response body type when content type is updated

rm : commented code

feat: update parser logic

fix: organize files

feat: enhance examples handling in collection export and import

feat: postman imports fro examples

feat: enhance OpenAPI import functionality to support examples

feat: support postman export

fix: postman export import

fix: open api tests, remove requestbody related logic

rm: examples

fix:  move common attributes files

ui fixes

fix: clone issue

fix: create example from request menu

review fixes

more review fixes

mv: files, fix mode req error

organize files

fix:tests

fix: save dot issue

fix: bugs

fix: postman export

fix: import path params

* chore:improve modal handling in environment and response example tests

fix: test issues resolved

* chore: update response example tests to use new fixture files and improve cleanup logic

---------

Co-authored-by: Abhishek S Lal <abhishek@usebruno.com>
Co-authored-by: Bijin Bruno <bijin@usebruno.com>
2025-11-01 05:56:11 +05:30
Abhishek S Lal
396ff2b196 test: playwright tests for create request with http, gRPC, ws, graphql (#5952)
* feat: added tests for request creation

* fix: add folder.bru

* add: common locators to locators.ts file

* refactor: update locators for request actions and improve test assertions across GraphQL, gRPC, HTTP, and WebSocket request tests

* fix: updated locator logic for folder requests.

* chore: implement cleanup logic for GraphQL, gRPC, HTTP, and WebSocket request tests

---------

Co-authored-by: sanish-bruno <sanish@usebruno.com>
Co-authored-by: Bijin Bruno <bijin@usebruno.com>
2025-10-31 22:04:35 +05:30
lohit
6826e98945 fix(bru-1447): json response formatting using fast-json-format library (#5932) 2025-10-31 20:34:36 +05:30
Siddharth Gelera (reaper)
e47d1ed353 Fix: safe serialise TypedArrays to avoid loosing constructor information (#5941)
* fix: enhance cleanJson to support serialization of typed arrays

* fix: correctness of inference based checks

* fix: remove duplicate Uint8Array reference

* fix: correct export syntax for mixinTypedArrays

Updated the export statement to use 'exports' instead of 'export' for proper module export functionality.

* chore: code cleanup

* test: add basics tests for cleanJson
2025-10-31 17:23:48 +05:30
sanish chirayath
08c182a875 Feat: support bin header in gRPC (#5869)
* Fix -bin header handling in grpc

* fix: bin-header, tests

rm: tests

rm: unused

fix: bin header

fix: test

fix: test

rm: un-necessarycode

---------

Co-authored-by: Juan Pablo Orsay <jporsay@gmail.com>
2025-10-31 17:07:12 +05:30
Abhishek S Lal
f3afb4bf84 Fix/export folder and collection level scripts (#5942)
* fix: bruno to postman export - export pre and post request scripts on folder/collection level

* refactor: removed redundant code.

* fix: lint error - file should end with a new line

---------

Co-authored-by: Jakub Sadowski <jakubsadowski08@gmail.com>
Co-authored-by: Bijin A B <bijin@usebruno.com>
2025-10-30 19:54:07 +05:30
lohit
21e8615247 fix(bru-1142): collection and global environments import and export functionality updates (#5910) 2025-10-30 19:37:44 +05:30
Abhishek S Lal
6e8751a27a Fix/client cert passphrase issues (#5898)
* fix: added interpolation, warning and syntax highlight for passphrase input

Changes:
1) When users add plain text in passphrase, warning message will be shown.
2) Passphrase will be interpolated from environment
3) Syntax highlighting for variables added.

Closes #2685

* fix: global environment variables interpolation in cert passphrase implemented.

* refactor: indentation refactoring
2025-10-30 18:14:39 +05:30
Abhishek S Lal
c9a96ee94f feat: fuzzy search for grpc methods list (#5940)
* feat: implemented fuzzy search in grpc methods

Changes:
1) New SearchInput reusable component created.
2) Search input box added in grpc methods list.
3) Fuzzy search and keyboard navigation functionality implemented.

Closes #5683

* feat: e2e test cases added for new grpc method searchbox

* fix: package-lock json update

* fix: added missing collection files for testing

* fix: fixed lint issue

* chore: update package-lock json file

* fix: improve keyboard navigation and search handling in MethodDropdown

Changes:
1) Adjusted focused index logic for ArrowUp key to remove focus after first item.
2) Enhanced handleSearchChange logic to highlight first item when search text is not empty.

* feat: implemented fuzzy search in grpc methods

Changes:
1) New SearchInput reusable component created.
2) Search input box added in grpc methods list.
3) Fuzzy search and keyboard navigation functionality implemented.

Closes #5683

* feat: e2e test cases added for new grpc method searchbox

* fix: added missing collection files for testing

* fix: fixed lint issue

* chore: update package-lock json file

* fix: improve keyboard navigation and search handling in MethodDropdown

Changes:
1) Adjusted focused index logic for ArrowUp key to remove focus after first item.
2) Enhanced handleSearchChange logic to highlight first item when search text is not empty.

* test: updated test description and some code optimisation
2025-10-30 17:56:49 +05:30
naman-bruno
b69db7b44b fix: High CPU due to WMI queries (#5924)
* fix: High CPU due to WMI queries
2025-10-30 15:32:21 +05:30
naman-bruno
73caaef42b fix: crash on viewing large responses (#5647)
* fix: crash on viewing large responses
2025-10-30 13:29:53 +05:30
Sanjai Kumar
e68b2ae3b7 feat: Import Insomnia environments (#5716)
* feat: Implement environment conversion utilities for Insomnia to Bruno migration

fix tests

fix: test

feat: updated `toBrunoEnv` and merging functions to flatten environment data using dot-notation keys. added tests for `buildV5Environments` and `buildV4Environments` to verify flattened key behavior and shallow overrides.

chore: update package-lock.json

refactor: replace `flat` library with custom `flattenObject` utility for improved environment data flattening

chore: remove package-lock.json updates

feat: update `toBrunoEnv` to convert environment values to strings and adjust tests for flattened key behavior in Insomnia environment imports

refactor: update flattening logic to use JavaScript-style square bracket notation for arrays and adjust related tests

feat: enhance insomnia-to-bruno conversion by normalizing variables in requests, and add tests for v4 and v5 environment imports

refactor: improve variable naming and streamline environment building logic in `buildV5Environments` and `buildV4Environments` functions

test: add cleanup step to environment import tests and update expected version for new feature

* revert package-lock.json changes

* test: Add data-testid attributes to environment variable rows in EnvironmentVariables component
2025-10-29 19:04:09 +05:30
Pooja
cc7f1ea58f feat: add copy and paste functionality for requests (#5907) 2025-10-29 17:24:09 +05:30
Sid
6e8cd55b76 Refactor: Change how test runner handles pageWithUserData tests (#5922)
* refactor: change how test runner opens pageWithUserData instances

* fix: test move tabs

* fix: custom ca cert tests

* fix: update file patterns and improve error messages

* fix: improve electron app launch logic

* fix: update temporary directory handling for Electron app

* fix: ensure newline at end of file in index.ts

This change adds a newline at the end of the file to comply with coding standards.

* fix: improve error handling in recursiveCopy function

- Simplified error message when source path does not exist.
- Enhanced error handling to provide clearer guidance on usage of `page` fixture.

* fix(e2e): close collections after each tests

* fix: reuse the worker instance per file instead of per user data dir

* fix: revert ssl tests as serial run is fixed

* fix: change afterEach to afterAll for cleanup

fix: change afterEach to afterAll for cleanup

---------

Co-authored-by: Bijin Bruno <bijin@usebruno.com>
2025-10-29 14:32:45 +05:30
Tanishq Singla
384aabf2af Bugfix: Error importing curl with no space in header (#5897)
* fix: error importing curl when there is no space in header
2025-10-29 14:11:46 +05:30
Abhishek S Lal
a15dcdb133 fix: removed unwanted logging of global environment variables in the console (#5904) 2025-10-29 13:57:57 +05:30
Abhishek S Lal
18848cdb26 fix: added xml-formatter package in package.json of bruno-js (#5920)
* fix: added xml-formatter package in package.json of bruno-js

* fix: package lock json update
2025-10-28 18:42:34 +05:30
Siddharth Gelera (reaper)
29b90a7e0d Fix: Multi sub protocol support for web sockets (#5903)
* fix: manually split sub proto for `ws` compat

* feat: force persist multi map for protocols

* fix: remove unnecessary pause in subprotocol tests
2025-10-28 11:34:24 +05:30
Bijin A B
4fbe371eb0 fix: revert increasing playwright worker count (#5906)
* fix: revert increasing playwright worker count
2025-10-27 22:54:54 +05:30
Pragadesh-45
6fd2b8be6d fix: enhance cleanup process in MaskedEditor by adding destroy method and improving disable logic (#5748) 2025-10-27 18:00:52 +05:30
Chirag Chandrashekhar
be7f92d77f Resolved issue: https://github.com/usebruno/bruno/issues/4672 (#5900)
- Added interpolation to setVar method's value field.
- Added playwright test to test the fix.
- Added jest test to test out the fix.

---
Playwright - PASS
Jest - PASS
---
2025-10-27 16:26:49 +05:30
Sanjai Kumar
c5325c732f feat: enhance environment variable persistence handling (#5783)
* feat: enhance environment variable persistence handling

* feat: experiment playwright with multiple workers

---------

Co-authored-by: Bijin Bruno <bijin@usebruno.com>
2025-10-25 19:02:45 +05:30
Anton
a538b27f24 Import WSDL to collection (#5015)
* Import WSDL to bruno collection

* feat(wsdl-import): remove unused code and minor refactor

---------

Co-authored-by: Bijin Bruno <bijin@usebruno.com>
2025-10-25 15:20:18 +05:30
Anoop M D
77bb8f40fe Update readme.md (#5883)
Fix broken anchors for contribute and git headers.
2025-10-24 17:56:21 +05:30
kosarinin
8f1f5e3861 Update readme.md
Fix broken anchors for contribute and git headers.
2025-10-24 15:12:40 +03:00
Bijin A B
e9251a1f3f fix: add missing jsonwebtoken in package-lock (#5882) 2025-10-24 17:25:11 +05:30
Sid
3a011b2a18 Merge pull request #5881 from usebruno/bugfix/incorrect-space-encode-internal
Fixes: Generate Code does urlencoding twice
2025-10-24 17:17:40 +05:30
Chirag Chandrashekhar
fa1498e2a8 Bugfix/incorrect space encode (#5870)
* Fix the space encoding issue

* fix: incorrect space encoding

Fixed an issue in Code Generation for requests. The original fix was
raised in [PR](https://github.com/usebruno/bruno/pull/4478). The current
PR fixes some merge conflicts and resolves some unimported dependencies
error.

* test: add URL encoding tests for code generation feature

Add Playwright tests to verify proper URL encoding behavior in Bruno's
code generation dialog for both encoded and unencoded query parameters.

* moved the test script inside request

* updated the snippet generation code to reuse code and reduce redundancy

* removed redundant code and reverted autoformat

* reverted some auto formatted changes

* reverting format during commit hook

* chore: reset formatting

* chore: reformat

---------

Co-authored-by: Vipin Sundar <86339268+vipin-sundar@users.noreply.github.com>
Co-authored-by: Chirag Chandrashekhar <chiragchan@Chirags-MacBook-Air.local>
Co-authored-by: Sid <siddharth@usebruno.com>

chore: reformat
2025-10-24 16:29:22 +05:30
Siddharth Gelera (reaper)
77681ca51e fix: inherit vars and headers from the collection (#5876)
* fix: inherit vars and headers from the collection
2025-10-24 15:08:10 +05:30
Vipin Sundar
045141efaf Fix the space encoding issue (#4478) 2025-10-23 15:27:19 +05:30
anusree-bruno
c997b91698 added jsonwebtoken as inbuilt library (#5535)
* added jsonwebtoken as inbuilt library

* removed bundling

* handle callback in quickjs

* chore: tests folder restructure

* chore: lint fix

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-10-22 14:57:19 +05:30
anusree-bruno
986d5b0b2a moved custom search to components folder (#5750)
* moved custom search to components folder

* renamed custom search

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-10-22 14:56:57 +05:30
ganesh
a2a521477a add fix for runtime var color (#4254)
* added new changes

* adds color to light and dark theme file

* import theme obj and use variable runtime color

* fix: operator linebreak style for eslint

* chore: remove un-needed changes

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-10-22 14:23:12 +05:30
Siddharth Gelera (reaper)
8e70adcbf9 fix: incomplete tests (#5824)
* fix: close support modal for other tests to reuse the window properly

* Update support-links.spec.js

* chore: reformat

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-10-22 13:39:36 +05:30
Prasanth Baskar
87296776fa add arch linux install to readme (#4569)
Signed-off-by: bupd <bupdprasanth@gmail.com>
2025-10-19 03:19:16 +05:30
Alex
9df70cd759 Merge pull request #5809 from 0x416c6578/feature/minify-json-xml
Add `bru.utils.minifyXml` and `bru.utils.minifyJson`
2025-10-19 01:29:32 +05:30
Anoop M D
8f9fb3b3c9 Merge pull request #5163 from josbiz/fix--dot-on-proxy-options-when-unused
fix: dot on unused proxy settings
2025-10-19 01:15:21 +05:30
Anoop M D
6d018f5648 Merge pull request #5164 from josbiz/fix--dot-now-showed-on-used-preset-setting
fix: show dot on used preset setting
2025-10-19 01:14:00 +05:30
Anoop M D
789d0b23c0 added option to revert changes (#4503) 2025-10-19 01:09:07 +05:30
anusree-bruno
81e1e403e4 handle options in getBody for QuickJS VM (#4614) 2025-10-19 01:02:30 +05:30
Sanjai Kumar
ad2add4026 Added tests for replacing invalid variable characters in Postman collection Env (#4634)
---------

Co-authored-by: sanjai0py <sanjailucifer666@gmail.com>
Co-authored-by: Anoop M D <anoop@usebruno.com>
2025-10-19 01:00:19 +05:30
naman-bruno
02554c3ad9 Merge pull request #4279 from naman-bruno/feat/apikey-codegen
Add: API Key auth in code generator
2025-10-19 00:53:58 +05:30
Anoop M D
62815e3429 Merge pull request #5008 from anusree-bruno/feat/add-process-env-vars-to-gql-introspection
add process.env variable support to GraphQL introspection
2025-10-18 23:15:51 +05:30
Anoop M D
9859b69559 Merge pull request #5113 from apealpha/bugfix/3019-prettify-json-with-variables
fix(request): prettify JSON with variables
2025-10-18 22:51:10 +05:30
Anoop M D
440c688bbb Merge pull request #4708 from pooja-bruno/improve/use-common-getTreePathFromCollectionToItem-function
improve: use common getTreePathFromCollectionToItem function
2025-10-18 22:44:08 +05:30
Anoop M D
416eb754b7 Merge pull request #4747 from ZieglerZhu/bugfix/update-readme-cn
docs(readme): update readme_cn.md
2025-10-18 18:00:11 +05:30
Anoop M D
b85d6efa60 Merge pull request #5303 from usebruno/dependabot/github_actions/actions/checkout-5
build(deps): bump actions/checkout from 4 to 5
2025-10-18 16:49:35 +05:30
Blake Guilloud
19dea18629 Merge pull request #5829 from BlakeGuilloud/bugfix/5823-saving-url-in-response-pane
Bugfix/5823 saving url in response pane
2025-10-18 16:39:21 +05:30
Abhishek S Lal
636901c23d fix: resolve global env variable becoming undefined on script execution (#5816)
* fix: resolve global env variable becoming undefined on script execution

Fixes an issue where global disabled environment variables were becoming undefined during request execution when the pre request script is non-empty.

The update ensures that global variables persist as expected and are correctly referenced throughout the request lifecycle.

Closes #5772.

* feat: added test for checking proper global env update through scripts

* refactor: updated comments for more readability and added a new data-testid in modal.
2025-10-17 21:50:50 +05:30
lohit
a4b1941817 fix(bru-2035): form-urlencoded logic updates (#5820) 2025-10-17 18:22:43 +05:30
Sid
7d8fde9180 fix: improve URL parsing in getParsedWsUrlObject (#5822) 2025-10-17 18:15:15 +05:30
Anoop M D
4197304bf9 Merge pull request #5679 from mheidinger/visual-gql-indicator
feat: add visual indicator for GQL requests
2025-10-17 15:00:37 +05:30
Max Heidinger
b75422a010 feat: add visual indicator for GQL requests 2025-10-17 10:25:54 +02:00
Pragadesh-45
e9f03c46c7 tests: add tests for URN parsing (#5819) 2025-10-17 10:58:26 +05:30
Pragadesh-45
73e828621f fix: enhance URL parameter parsing and interpolation logic (#5812)
* fix: enhance URL parameter parsing and interpolation logic
2025-10-16 17:58:53 +05:30
Siddharth Gelera (reaper)
2becf49542 fix: harden type checks for buildFormUrlEncodedPayload (#5811) 2025-10-16 13:31:13 +05:30
dependabot[bot]
e57162b79a build(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 17:10:22 +00:00
Jose Bolivar Ibz
1de9203dd5 Merge branch 'main' into fix--dot-on-proxy-options-when-unused 2025-08-29 08:45:44 -07:00
Jose Bolivar Ibz
cffa37ed50 fix: show dot on used preset setting 2025-07-22 12:17:19 -07:00
Jose Bolivar Ibz
bcf61f507a Update index.js
Variable change from let to const
2025-07-22 12:10:14 -07:00
Jose Bolivar Ibz
325b573da9 fix: dot on unused proxy settings 2025-07-22 11:51:02 -07:00
apealpha
4b5c7dcca6 fix(request): prettify JSON with variables 2025-07-16 00:00:26 +09:00
anusree-bruno
d2888daa88 add process.env variable support to GraphQL introspection 2025-06-30 13:12:35 +05:30
ZieglerZhu
ec9d63219f docs(readme): update readme_cn.md
- Added introduction of commercial version
- Added Table of Contents
- Updated installation guide via package managers
- Updated important links
- Adjusted document structure, optimized title hierarchy
2025-05-22 23:38:33 +08:00
pooja-bruno
9173ffbdee improve: use common getTreePathFromCollectionToItem function 2025-05-19 12:42:10 +05:30
anusree-bruno
5f112a318d added option to revert changes 2025-04-15 00:11:41 +05:30
1529 changed files with 116422 additions and 25121 deletions

66
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,66 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: 'en-US'
early_access: false
tone_instructions: 'You are an expert code reviewer in TypeScript, JavaScript, NodeJS, and ElectronJS. You work in an enterprise software developer team, providing concise and clear code review advice. You only elaborate or provide detailed explanations when requested.'
knowledge_base:
opt_out: false
code_guidelines:
enabled: true
filePatterns:
- '**/CODING_STANDARDS.md'
reviews:
profile: 'chill'
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
base_branches: ['main', 'release/*']
path_instructions:
- path: 'tests/**/**.*'
instructions: |
Review the following e2e test code written using the Playwright test library. Ensure that:
- Follow best practices for Playwright code and e2e automation
- Try to reduce usage of `page.waitForTimeout();` in code unless absolutely necessary and the locator cannot be found using existing `expect()` playwright calls
- Avoid using `page.pause()` in code
- Use locator variables for locators
- Avoid using test.only
- Use multiple assertions
- Promote the use of `test.step` as much as possible so the generated reports are easier to read
- Ensure that the `fixtures` like the collections are nested inside the `fixtures` folder
**Fixture Example***: Here's an example of possible fixture and test pair
```
.
├── fixtures
│ └── collection
│ ├── base.bru
│ ├── bruno.json
│ ├── collection.bru
│ ├── ws-test-request-with-headers.bru
│ ├── ws-test-request-with-subproto.bru
│ └── ws-test-request.bru
├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
├── headers.spec.ts
├── persistence.spec.ts
├── variable-interpolation
│ ├── fixtures
│ │ └── collection
│ │ ├── environments
│ │ ├── bruno.json
│ │ └── ws-interpolation-test.bru
│ ├── init-user-data
│ └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
└── subproto.spec.ts
```
chat:
auto_reply: true

View File

@@ -1,9 +1,10 @@
# Description
### Description
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
### Contribution Checklist:
#### Contribution Checklist:
- [ ] **I've used AI significantly to create this pull request**
- [ ] **The pull request only addresses one issue or adds one feature.**
- [ ] **The pull request does not introduce any breaking changes**
- [ ] **I have added screenshots or gifs to help explain the change if applicable.**
@@ -12,6 +13,6 @@
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
### Publishing to New Package Managers
#### Publishing to New Package Managers
Please see [here](../publishing.md) for more information.

View File

@@ -23,4 +23,5 @@ runs:
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

View File

@@ -25,7 +25,7 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
@@ -40,7 +40,7 @@ jobs:
run: |
cd packages/bruno-tests/collection
npm install
bru run --env Prod --output junit.xml --format junit
bru run --env Prod --output junit.xml --format junit --sandbox developer
- name: Publish Test Report
uses: dorny/test-reporter@v2

View File

@@ -15,7 +15,7 @@ jobs:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node Dependencies
uses: ./.github/actions/common/setup-node-deps
@@ -44,7 +44,7 @@ jobs:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node Dependencies
uses: ./.github/actions/common/setup-node-deps
@@ -73,7 +73,7 @@ jobs:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Node Dependencies
uses: ./.github/actions/common/setup-node-deps

View File

@@ -13,7 +13,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
@@ -30,6 +30,7 @@ jobs:
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
npm run build --workspace=packages/bruno-converters
npm run build --workspace=packages/bruno-requests
npm run build --workspace=packages/bruno-schema-types
npm run build --workspace=packages/bruno-filestore
- name: Lint Check
@@ -57,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
@@ -66,7 +69,7 @@ jobs:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
@@ -83,6 +86,7 @@ jobs:
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
npm run build --workspace=packages/bruno-converters
npm run build --workspace=packages/bruno-requests
npm run build --workspace=packages/bruno-schema-types
npm run build --workspace=packages/bruno-filestore
- name: Run Local Testbench
@@ -94,7 +98,7 @@ jobs:
run: |
cd packages/bruno-tests/collection
npm install
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit --sandbox developer
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action@v2
@@ -108,7 +112,7 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v5
with:
node-version: v22.11.x
@@ -134,12 +138,13 @@ jobs:
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
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: playwright-report

11
.gitignore vendored
View File

@@ -48,6 +48,17 @@ yarn-error.log*
bruno.iml
.idea
.vscode
.cursor
# Playwright
/blob-report/
# Development plan files
CLAUDE.md
*.plan.md
# packages dist
packages/bruno-filestore/dist
packages/bruno-requests/dist
packages/bruno-schema-types/dist
packages/bruno-converters/dist

2
.nvmrc
View File

@@ -1 +1 @@
v22.11.0
v22.12.0

78
CODING_STANDARDS.md Normal file
View File

@@ -0,0 +1,78 @@
# Bruno Coding Standards
- No diffs unless an actual change is made, the code changes need to be as minimal as possible, avoid making un-necessary whitespace diffs. This is already handled by eslint but make sure you check your code changes before commiting and raising a PR.
## General Style Rules
- Use 2 spaces for indentation. No tabs, just spaces keeps everything neat and uniform.
- Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., <svg xmlns="..." viewBox="...">) to follow React conventions.
- Always add semicolons at the end of statements. It's like putting a period at the end of a sentence clarity matters.
- JSX is enabled, so feel free to use it where it makes sense.
## Punctuation and Spacing
- No trailing commas. Keep it clean, no extra commas hanging around.
- Always use parentheses around parameters in arrow functions. Even for single params consistency is key.
- For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline.
- No newlines inside function parentheses. Keep 'em tight.
- Space before and after the arrow in arrow functions. `() => {}` is good.
- No space between function name and parentheses. `func()` not `func ()`.
- Semicolons go at the end of the line, not on a new line.
- No strict max length write readable code, not cramped lines.
- Multiple expressions per line in JSX are fine flexibility is nice.
Remember, these rules are here to make our codebase harmonious. If something doesn't fit perfectly, let's chat about it. Happy coding! 🚀
## Tests
- Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created.
- Prioritise high-value tests over maximum coverage. Focus on testing behaviour that is critical, complex, or likely to break—dont chase coverage numbers for their own sake.
- Write behaviour-driven tests, not implementation-driven ones. Tests should validate real expected output and observable behaviour, not internal details or mocked-out logic unless absolutely necessary.
- Minimise mocking unless it meaningfully increases clarity or isolates external dependencies. Prefer real flows where practical; only mock external services, slow systems, or non-deterministic behaviour.
- Keep tests readable and maintainable. Optimise for clarity over cleverness. Name tests descriptively, keep setup minimal, and avoid unnecessary abstraction.
- Aim for tests that fail usefully. When a test fails, it should clearly indicate what behaviour broke and why.
- Cover both the “happy path” and the realistically problematic paths. Validate expected success behaviour, but also validate error handling, edge cases, and degraded-mode behaviour when appropriate.
- Ensure tests are deterministic and reproducible. No randomness, timing dependencies, or environment-specific assumptions without explicit control.
- Avoid overfitting tests to current behaviour if future flexibility matters. Only assert what needs to be true, not incidental details.
- Use consistent patterns and helper utilities where they improve clarity. Prefer shared test utilities over copy-pasted setup code, but only when it actually reduces complexity.
- Tests should be fast enough to run continuously. Avoid long-running operations unless absolutely necessary; prefer lightweight fixtures and isolated units.
## UI Specific instructions
### React
- 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.
## Readability and Abstractions
- Avoid abstractions unless the exact same code is being used in more than 3 places.
- Names for functions need to be concise and descriptive.
- Add in JSDoc comments to add more details to the abstractions if needed.
- Follow functional programming but just enough to be readable, we don't need to go as deep as ADTs and Monads, we want to keep the code pipeline obvious and easy for everyone to read and contribute to.
- Avoid single line abstractions where all that's being done is increasing the call stack with one additional function.
- Add in meaningful comments instead of obvious ones where complex code flow is explained properly.

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

@@ -16,6 +16,7 @@
| [日本語](docs/contributing/contributing_ja.md)
| [हिंदी](docs/contributing/contributing_hi.md)
| [Dutch](docs/contributing/contributing_nl.md)
| [فارسی](docs/contributing/contributing_fa.md)
## Let's make Bruno better, together!!
@@ -69,11 +70,13 @@ npm run build:bruno-query
npm run build:bruno-common
npm run build:bruno-converters
npm run build:bruno-requests
npm run build:schema-types
npm run build:bruno-filestore
# bundle js sandbox libraries
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
```
##### Option 2
```bash
@@ -94,18 +97,22 @@ npm run dev:electron
```
##### Option 2
```bash
# run electron and react app concurrently
npm run dev
```
#### Customize Electron `userData` path
If `ELECTRON_USER_DATA_PATH` env-variable is present and its development mode, then `userData` path is modified accordingly.
e.g.
```sh
ELECTRON_USER_DATA_PATH=$(realpath ~/Desktop/bruno-test) npm run dev:electron
```
This will create a `bruno-test` folder on your Desktop and use it as the `userData` path.
### Troubleshooting

View File

@@ -0,0 +1,92 @@
[English](../../contributing.md)
## با هم، Bruno را بهتر می‌کنیم!
خوشحالم که قصد دارید Bruno را بهبود ببخشید. در ادامه قوانین و راهنماها برای راه‌اندازی Bruno روی سیستم شما آورده شده است.
### فناوری‌های استفاده‌شده
به فارسی برونو Bruno با استفاده از Next.js و React ساخته شده است. همچنین از Electron برای بسته‌بندی نسخه دسکتاپ (که امکان مجموعه‌های محلی را فراهم می‌کند) استفاده می‌کنیم.
کتابخانه‌هایی که استفاده می‌کنیم:
- CSS - Tailwind استایل
- Codemirror - ویرایشگر کد
- Redux - مدیریت وضعیت
- Tabler Icons - آیکون‌ها
- formik - فرم‌ها
- Yup اعتبارسنجی اسکیمـا
- axios - کلاینت درخواست
- chokidar - پایش‌گر سیستم فایل
### پیش‌نیازها
شما به [نود v20.x یا اخرین نسخه پایدار](https://nodejs.org/en/) و npm 8.x نیاز دارید. در این پروژه از فضای کاری npm (npm workspaces) استفاده می‌کنیم.
### شروع به کدنویسی
برای راه‌اندازی محیط توسعه محلی به فایل [مستندات توسعه](docs/development_fa.md) مراجعه کنید:
### ارسال Pull Request
1 - لطفاً Pull Requestها (PR) را کوتاه و متمرکز نگه دارید و تنها یک هدف مشخص را دنبال کنند. </br>
2 - لطفاً از فرمت نام‌گذاری شاخه‌ها استفاده کنید:
- feature/[name]: این شاخه باید شامل یک قابلیت مشخص باشد.
- feature/dark-mode : مثال
- bugfix/[name]: این شاخه باید تنها شامل رفع یک باگ مشخص باشد.
- bugfix/bug-1 : مثال
## توسعه
به فارسی برونو یا Bruno به‌صورت یک اپلیکیشن «سنگین» توسعه داده می‌شود. برای اجرا باید ابتدا Next.js را در یک پنجره ترمینال اجرا کنید و سپس اپلیکیشن Electron را در پنجره ترمینال دیگری راه‌اندازی نمایید.
### نیازمندی توسعه
- NodeJS v18
### اجرای محلی
```bash
# از ورژن NodeJS 18 استفاده کنید
nvm use
# نصب وابستگی‌ها
npm i --legacy-peer-deps
# ساخت مستندات GraphQL
npm run build:graphql-docs
# ساخت bruno-query
npm run build:bruno-query
# اجرای اپ Next (ترمینال 1)
npm run dev:web
# اجرای اپ Electron (ترمینال 2)
npm run dev:electron
```
### عیب‌یابی
ممکن است هنگام اجرای `npm install` خطای `Unsupported platform` ببینید. برای رفع این مشکل، پوشه `node_modules` و فایل `package-lock.json` را حذف کرده و سپس دوباره `npm install` را اجرا کنید. این کار معمولاً همه پکیج‌های لازم را نصب می‌کند.
```shell
# حذف پوشه node_modules در زیردایرکتوری‌ها
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# حذف فایل package-lock.json در زیردایرکتوری‌ها
find . -type f -name "package-lock.json" -delete
```
### تست‌ها
```bash
# اجرای تست‌های schema مربوط به bruno
npm test --workspace=packages/bruno-schema
# اجرای تست‌ها در همه فضاهای کاری (در صورت وجود)
npm test --workspaces --if-present
```

View File

@@ -0,0 +1,8 @@
[English](../../publishing.md)
### انتشار Bruno در یک پکیج منیجر جدید
اگرچه کد ما متن‌باز است و همه می‌توانند از آن استفاده کنند، لطفاً قبل از انتشار Bruno در مدیر بسته‌های جدید با ما تماس بگیرید. به عنوان سازنده Bruno، علامت تجاری `Bruno` را برای این پروژه دارم و مایلم توزیع آن را مدیریت کنم. اگر دوست دارید Bruno را در یک مدیر بسته جدید ببینید، لطفاً یک issue در گیت‌هاب ثبت کنید.
اگرچه بیشتر قابلیت‌های ما رایگان و متن‌باز هستند (شامل REST و GraphQL Apis)،
ما تلاش می‌کنیم بین اصول متن‌باز و توسعه پایدار تعادل مناسبی برقرار کنیم - https://github.com/usebruno/bruno/discussions/269

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)
@@ -41,13 +41,6 @@
![bruno](/assets/images/landing-2.png) <br /><br />
### الطبعة الذهبية ✨
غالبية ميزاتنا مجانية ومفتوحة المصدر.
نحن نسعى لتحقيق توازن متناغم بين [مبادئ الشفافية والاستدامة](https://github.com/usebruno/bruno/discussions/269)
طلبات الشراء لـ [الطبعة الذهبية](https://www.usebruno.com/pricing) ستطلق قريبًا بسعر ~~$19~~ **$9** ! <br/>
[اشترك هنا](https://usebruno.ck.page/4c65576bd4) لتصلك إشعارات عند الإطلاق.
### التثبيت

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)
@@ -37,13 +37,37 @@ Bruno 直接在您的电脑文件夹中存储您的 API 信息。我们使用纯
Bruno 仅限离线使用。我们计划永不向 Bruno 添加云同步功能。我们重视您的数据隐私,并认为它应该留在您的设备上。阅读我们的长期愿景 [点击查看](https://github.com/usebruno/bruno/discussions/269)
[下载 Bruno](https://www.usebruno.com/downloads)
📢 观看我们在印度 FOSS 3.0 会议上的最新演讲 [点击查看](https://www.youtube.com/watch?v=7bSMFpbcPiY)
![bruno](../../assets/images/landing-2.png) <br /><br />
### 安装
## 商业版本 ✨
Bruno 可以在我们的 [网站上下载](https://www.usebruno.com/downloads) Mac、Windows 和 Linux 的可执行文件
我们的大多数功能都是免费且开源的
我们致力于在 [开源与可持续性发展](https://github.com/usebruno/bruno/discussions/269) 之间取得和谐的平衡
欢迎使用我们的 [付费版本](https://www.usebruno.com/pricing) ,看看附加的功能是否对您或团队有所帮助! <br/>
## 目录
- [安装](#安装)
- [特性](#特性)
- [跨平台使用 🖥️](#跨平台使用-)
- [通过Git协作 👩‍💻🧑‍💻](#通过git协作-)
- [重要链接 📌](#重要链接-)
- [展示 🎥](#展示-)
- [分享评价 📣](#分享评价-)
- [发布到新的包管理器](#发布到新的包管理器)
- [联系方式 🌐](#联系方式-)
- [商标](#商标)
- [贡献 👩‍💻🧑‍💻](#贡献-)
- [作者](#作者)
- [许可证 📄](#许可证-)
## 安装
Bruno 可以在我们的 [网站上下载](https://www.usebruno.com/downloads) 适用于Mac、Windows 和 Linux 的可执行文件。
您也可以通过包管理器如 Homebrew、Chocolatey、Scoop、Snap 和 Apt 安装 Bruno。
@@ -58,9 +82,15 @@ choco install bruno
scoop bucket add extras
scoop install bruno
# 在 Windows 上用 winget 安装
winget install Bruno.Bruno
# 在 Linux 上用 Snap 安装
snap install bruno
# 在 Linux 上用 Flatpak 安装
flatpak install com.usebruno.Bruno
# 在 Linux 上用 Apt 安装
sudo mkdir -p /etc/apt/keyrings
sudo apt update && sudo apt install gpg curl
@@ -73,67 +103,50 @@ echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebr
sudo apt update && sudo apt install bruno
```
### 在 Mac 上通过 Homebrew 安装 🖥️
## 特性
### 跨平台使用 🖥️
![bruno](../../assets/images/run-anywhere.png) <br /><br />
### Collaborate 安装 👩‍💻🧑‍💻
### 通过Git协作 👩‍💻🧑‍💻
或者任何您选择的版本控制系统
![bruno](../../assets/images/version-control.png) <br /><br />
### 重要链接 📌
## 重要链接 📌
- [我们的愿景](https://github.com/usebruno/bruno/discussions/269)
- [路线图](https://github.com/usebruno/bruno/discussions/384)
- [路线图](https://www.usebruno.com/roadmap)
- [文档](https://docs.usebruno.com)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno)
- [网站](https://www.usebruno.com)
- [价格](https://www.usebruno.com/pricing)
- [下载](https://www.usebruno.com/downloads)
- [GitHub 赞助](https://github.com/sponsors/helloanoop).
### 展示 🎥
## 展示 🎥
- [Testimonials](https://github.com/usebruno/bruno/discussions/343)
- [Knowledge Hub](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### 支持 ❤️
如果您喜欢 Bruno 并想支持我们的开源工作,请考虑通过 [GitHub Sponsors](https://github.com/sponsors/helloanoop) 来赞助我们。
### 分享评价 📣
## 分享评价 📣
如果 Bruno 在您的工作和团队中帮助了您,请不要忘记在我们的 GitHub 讨论上分享您的 [评价](https://github.com/usebruno/bruno/discussions/343)
### 发布到新的包管理器
## 发布到新的包管理器
有关更多信息,请参见 [此处](../publishing/publishing_cn.md) 。
如需了解更多信息,请参见 [此处](../publishing/publishing_cn.md) 。
### 贡献 👩‍💻🧑‍💻
我很高兴您希望改进 bruno。请查看 [贡献指南](../contributing/contributing_cn.md)。
即使您无法通过代码做出贡献,我们仍然欢迎您提出 BUG 和新的功能需求。
### 作者
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### 联系方式 🌐
## 联系方式 🌐
[𝕏 (Twitter)](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### 商标
## 商标
**名称**
@@ -143,6 +156,20 @@ sudo apt update && sudo apt install bruno
Logo 源自 [OpenMoji](https://openmoji.org/library/emoji-1F436/). License: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### 许可证 📄
## 贡献 👩‍💻🧑‍💻
很高兴您希望改进 bruno。请查看 [贡献指南](../contributing/contributing_cn.md)。
即使您无法通过代码做出贡献,我们仍然欢迎您提出 BUG 和新的功能需求。
## 作者
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
## 许可证 📄
[MIT](../../license.md)

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)
@@ -43,13 +43,6 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
![bruno](/assets/images/landing-2.png) <br /><br />
### Golden Edition ✨
Die meisten unserer Funktionen sind kostenlos und quelloffen.
Wir bemühen uns um ein Gleichgewicht zwischen [Open-Source-Prinzipien und Nachhaltigkeit](https://github.com/usebruno/bruno/discussions/269)
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
### Installation
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.

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)
@@ -43,13 +43,6 @@ Bruno funciona sin conexión a internet. No tenemos intenciones de añadir sincr
![bruno](/assets/images/landing-2.png) <br /><br />
### Golden Edition ✨
La mayoría de nuestras funcionalidades son gratis y de código abierto.
Queremos alcanzar un equilibrio en armonía entre los [principios open-source y la sostenibilidad](https://github.com/usebruno/bruno/discussions/269).
¡Puedes reservar la [Golden Edition](https://www.usebruno.com/pricing) por ~~$19~~ **$9**! <br/>
### Instalación
Bruno está disponible para su descarga [en nuestro sitio web](https://www.usebruno.com/downloads) para Mac, Windows y Linux.

143
docs/readme/readme_fa.md Normal file
View File

@@ -0,0 +1,143 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### برونو یا Bruno - محیط توسعه متن باز برای تست و توسعه API ها
[![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)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](../../readme.md)
| [Українська](./readme_ua.md)
| [Русский](./readme_ru.md)
| [Türkçe](./readme_tr.md)
| [Deutsch](./readme_de.md)
| [Français](./readme_fr.md)
| [Português (BR)](./readme_pt_br.md)
| [한국어](./readme_kr.md)
| [বাংলা](./readme_bn.md)
| [Español](./readme_es.md)
| **فارسی**
| [Română](./readme_ro.md)
| [Polski](./readme_pl.md)
| [简体中文](./readme_cn.md)
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
برونو یک کلاینت API جدید و نوآورانه است که هدفش تغییر وضعیت فعلی ابزارهایی مانند Postman و سایر ابزارهای مشابه است.
برونو مجموعه‌های شما را مستقیماً در یک پوشه روی فایل‌سیستم شما ذخیره می‌کند. ما از یک زبان نشانه‌گذاری ساده به نام Bru برای ذخیره اطلاعات درخواست‌های API استفاده می‌کنیم.
شما می‌توانید برای همکاری روی مجموعه‌های API خود، از Git یا هر سیستم کنترل نسخه دلخواهتان استفاده کنید.
برونو فقط به صورت آفلاین کار می‌کند. هیچ برنامه‌ای برای اضافه کردن همگام‌سازی ابری به برونو در آینده وجود ندارد. ما به حریم خصوصی داده‌های شما اهمیت می‌دهیم و معتقدیم که باید روی دستگاه خودتان باقی بمانند. می‌توانید چشم‌انداز بلندمدت ما را مطالعه کنید. [اینجا (به انگلیسی)](https://github.com/usebruno/bruno/discussions/269)
📢 جدیدترین ارائه ما را در کنفرانس India FOSS 3.0 تماشا کنید.
[اینجا](https://www.youtube.com/watch?v=7bSMFpbcPiY)
![bruno](/assets/images/landing-2.png) <br /><br />
### نصب
برونو به صورت یک فایل باینری برای دانلود در دسترس است. [بر روی وبسایت ما](https://www.usebruno.com/downloads) برای مک لینکوس و ویندوز.
همچنین می‌توانید برونو را از طریق مدیر بسته‌هایی مانند Homebrew، Chocolatey، Snap و Apt نصب کنید.
```sh
# بر روی مک از طریق brew
brew install bruno
# بر روی ویندوز از طریق Chocolatey
choco install bruno
# بر روی لینوکس از طریق Snap
snap install bruno
# بر روی لینوکس از طریق Apt
sudo mkdir -p /etc/apt/keyrings
sudo apt update && sudo apt install gpg curl
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x9FA6017ECABE0266" \
| gpg --dearmor \
| sudo tee /etc/apt/keyrings/bruno.gpg > /dev/null
sudo chmod 644 /etc/apt/keyrings/bruno.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" \
| sudo tee /etc/apt/sources.list.d/bruno.list
sudo apt update && sudo apt install bruno
```
### روی پلتفرم‌های مختلف کار می‌کند 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### همکاری از طریق گیت 👩‍💻🧑‍💻
یا هر سیستم کنترل نسخه‌ای که ترجیح می‌دهید
![bruno](/assets/images/version-control.png) <br /><br />
### لینک‌های مهم 📌
- [آخرین نسخه پایدار ما](https://github.com/usebruno/bruno/discussions/269)
- [نقشه راه](https://github.com/usebruno/bruno/discussions/384)
- [مستندات](https://docs.usebruno.com)
- [وبسایت](https://www.usebruno.com)
- [اشتراک ها](https://www.usebruno.com/pricing)
- [دانلود](https://www.usebruno.com/downloads)
### ویدیوها 🎥
- [تجربه ها](https://github.com/usebruno/bruno/discussions/343)
- [مرکز دانش](https://github.com/usebruno/bruno/discussions/386)
- [اسکریپ مانیا](https://github.com/usebruno/bruno/discussions/385)
### حمایت ❤️
جوون! اگر این پروژه را دوست دارید، روی دکمه ⭐ کلیک کنید!
### تجربه‌های به اشتراک گذاشته‌شده 📣
اگر برونو به شما یا تیمتان کمک کرده است، لطفاً فراموش نکنید تجربه‌های خود را به اشتراک بگذارید. [تجربه‌های خود را در بحث گیت‌هاب ما به اشتراک بگذارید](https://github.com/usebruno/bruno/discussions/343).
### انتشار برونو در یک پکیچ منیجر جدید
لطفا چک بکنید [اینجارو](../../publishing.md) برای اطلاعات بیشتر.
### مشارکت 👩‍💻🧑‍💻
خوشحالم که می‌خواهید برونو را بهتر کنید. لطفا [راهنمای مشارکت را بررسی کنید](../contributing/contributing_fa.md).
حتی اگر نمی‌توانید از طریق کدنویسی مشارکت کنید، در گزارش باگ‌ها و درخواست قابلیت‌های جدید که به حل نیازهای شما کمک می‌کند تردید نکنید.
### نویسنده ها
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### در ارتباط باشید 🌐
[𝕏 (تویتر)](https://twitter.com/use_bruno) <br />
[وبسایت](https://www.usebruno.com) <br />
[دیسکورد](https://discord.com/invite/KgcZUncpjq) <br />
[لینکدین](https://www.linkedin.com/company/usebruno)
### برند
**نام**
به فارسی برونو - `Bruno` یک علامت تجاری ثبت‌شده متعلق به [Anoop M D](https://www.helloanoop.com/)
**لوگو**
لوگو توسط [OpenMoji](https://openmoji.org/library/emoji-1F436/) ساخته شده است. مجوز: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### مجوز 📄
[MIT](../../license.md)

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)
@@ -43,13 +43,6 @@ Bruno はオフラインのみで利用できます。Bruno にクラウド同
![bruno](/assets/images/landing-2.png) <br /><br />
### ゴールデンエディション ✨
機能のほとんどが無料で使用でき、オープンソースとなっています。
私たちは[オープンソースの原則と長期的な維持](https://github.com/usebruno/bruno/discussions/269)の間でうまくバランスを取ろうと努力しています。
[ゴールデンエディション](https://www.usebruno.com/pricing)を **19 ドル** (買い切り)で購入できます!
### インストール方法
Bruno は[私たちのウェブサイト](https://www.usebruno.com/downloads)からバイナリをダウンロードできます。Mac, Windows, Linux に対応しています。

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)
@@ -43,12 +43,6 @@
![bruno](../../assets/images/landing-2.png) <br /><br />
### ოქროს გამოცემა ✨
მთავარი ფუნქციების უმეტესობა უფასოა და ღია წყაროა. ჩვენ ვცდილობთ ჰარმონიული ბალანსის დაცვას [ღია წყაროების პრინციპებსა და მდგრადობას შორის](https://github.com/usebruno/bruno/discussions/269)
თქვენ შეგიძლიათ შეიძინოთ [ოქროს გამოცემა](https://www.usebruno.com/pricing) ერთჯერადი გადახდით **19 დოლარად**! <br/>
### ინსტალაცია
ბრუნო ხელმისაწვდომია როგორც ბინარული ჩამოტვირთვა [ჩვენ的网站上](https://www.usebruno.com/downloads) Mac-ის, Windows-ისა და Linux-ისთვის.

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)
@@ -26,13 +26,6 @@ Bruno is uitsluitend offline. Er zijn geen plannen om ooit cloud-synchronisatie
![bruno](/assets/images/landing-2.png) <br /><br />
### Golden Edition ✨
De meeste van onze functies zijn gratis en open source.
We streven naar een harmonieuze balans tussen [open-source principes en duurzaamheid](https://github.com/usebruno/bruno/discussions/269).
Je kunt de [Golden Edition](https://www.usebruno.com/pricing) kopen voor een eenmalige betaling van **$19**! <br/>
### Installatie
Bruno is beschikbaar als binaire download [op onze website](https://www.usebruno.com/downloads) voor Mac, Windows en Linux.

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)
@@ -41,13 +41,6 @@ Bruno é totalmente offline. Não há planos de adicionar sincronização em nuv
![bruno](../../assets/images/landing-2.png) <br /><br />
### Golden Edition ✨
A grande maioria dos nossos recursos são gratuitos e de código aberto.
Nós nos esforçamos para encontrar um equilíbrio harmônico entre [princípios de código aberto e sustentabilidade](https://github.com/usebruno/bruno/discussions/269)
Você pode pré encomendar o plano [Golden Edition](https://www.usebruno.com/pricing) por ~~USD $19~~ **USD $9**! <br/>
### Instalação
Bruno está disponível para download como binário [em nosso site](https://www.usebruno.com/downloads) para Mac, Windows e Linux.

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)

View File

@@ -1,20 +1,32 @@
// eslint.config.js
const { defineConfig } = require("eslint/config");
const globals = require("globals");
const { defineConfig } = require('eslint/config');
const globals = require('globals');
const { fixupPluginRules } = require('@eslint/compat');
const eslintPluginDiff = require('eslint-plugin-diff');
let stylistic;
const runESMImports = async () => {
stylistic = await import('@stylistic/eslint-plugin').then(d => d.default);
stylistic = await import('@stylistic/eslint-plugin').then((d) => d.default);
};
module.exports = runESMImports().then(() => defineConfig([
// Global ignores - must be a standalone object with ONLY ignores
{
ignores: [
'**/node_modules/**/*',
'**/dist/**/*',
'**/*.bru',
'packages/bruno-js/src/sandbox/bundle-browser-rollup.js',
'packages/bruno-app/public/static/**/*',
'packages/bruno-app/.next/**/*',
'packages/bruno-electron/web/**/*'
]
},
{
plugins: {
'diff': fixupPluginRules(eslintPluginDiff),
'@stylistic': stylistic,
'@stylistic': stylistic
},
languageOptions: {
parser: require('@typescript-eslint/parser'),
@@ -26,6 +38,7 @@ module.exports = runESMImports().then(() => defineConfig([
files: [
'./eslint.config.js',
'tests/**/*.{ts,js}',
'playwright/**/*.{js,ts}',
'packages/bruno-app/**/*.{js,jsx,ts}',
'packages/bruno-app/src/test-utils/mocks/codemirror.js',
'packages/bruno-cli/**/*.js',
@@ -33,18 +46,19 @@ module.exports = runESMImports().then(() => defineConfig([
'packages/bruno-converters/**/*.js',
'packages/bruno-electron/**/*.js',
'packages/bruno-filestore/**/*.ts',
'packages/bruno-schema-types/**/*.ts',
'packages/bruno-js/**/*.js',
'packages/bruno-lang/**/*.js',
'packages/bruno-requests/**/*.ts',
'packages/bruno-requests/**/*.js',
'packages/bruno-tests/**/*.{js,ts}'
],
processor: 'diff/diff',
rules: {
...stylistic.configs.customize({
indent: 2,
quotes: 'single',
semi: true,
jsx: true,
jsx: true
}).rules,
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
@@ -52,9 +66,9 @@ module.exports = runESMImports().then(() => defineConfig([
'@stylistic/curly-newline': ['error', {
multiline: true,
minElements: 2,
consistent: true,
consistent: true
}],
'@stylistic/function-paren-newline': ['error', 'never'],
'@stylistic/function-paren-newline': ['off'],
'@stylistic/array-bracket-spacing': ['error', 'never'],
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
'@stylistic/function-call-spacing': ['error', 'never'],
@@ -62,12 +76,14 @@ module.exports = runESMImports().then(() => defineConfig([
'@stylistic/padding-line-between-statements': ['off'],
'@stylistic/semi-style': ['error', 'last'],
'@stylistic/max-len': ['off'],
'@stylistic/jsx-one-expression-per-line': ['off']
},
'@stylistic/jsx-one-expression-per-line': ['off'],
'@stylistic/max-statements-per-line': ['off'],
'@stylistic/no-mixed-operators': ['off']
}
},
{
files: ["packages/bruno-app/**/*.{js,jsx,ts}"],
ignores: ["**/*.config.js", "**/public/**/*"],
files: ['packages/bruno-app/**/*.{js,jsx,ts}'],
ignores: ['**/*.config.js', '**/public/**/*'],
languageOptions: {
globals: {
...globals.browser,
@@ -80,114 +96,126 @@ module.exports = runESMImports().then(() => defineConfig([
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
jsx: true
}
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
// It prevents lint errors when using CommonJS exports (module.exports) in Jest mocks.
files: ["packages/bruno-app/src/test-utils/mocks/codemirror.js"],
files: ['packages/bruno-app/src/test-utils/mocks/codemirror.js'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
...globals.jest
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-cli/**/*.js"],
ignores: ["**/*.config.js"],
// Storybook config files use CommonJS with __dirname and module.exports
files: ['packages/bruno-app/storybook/**/*.js'],
languageOptions: {
globals: {
...globals.node
}
},
rules: {
'no-undef': 'error'
}
},
{
files: ['packages/bruno-cli/**/*.js'],
ignores: ['**/*.config.js'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parserOptions: {
ecmaVersion: "latest"
},
ecmaVersion: 'latest'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-common/**/*.ts"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-common/**/*.ts'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parser: require("@typescript-eslint/parser"),
parser: require('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./packages/bruno-common/tsconfig.json",
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './packages/bruno-common/tsconfig.json'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-converters/**/*.js"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-converters/**/*.js'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
ecmaVersion: 'latest',
sourceType: 'module'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-electron/**/*.js"],
ignores: ["**/*.config.js", "**/web/**/*"],
files: ['packages/bruno-electron/**/*.js'],
ignores: ['**/*.config.js', '**/web/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
...globals.jest
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-filestore/**/*.ts"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-filestore/**/*.ts'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parser: require("@typescript-eslint/parser"),
parser: require('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./packages/bruno-filestore/tsconfig.json",
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './packages/bruno-filestore/tsconfig.json'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-js/**/*.js"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-js/**/*.js'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
@@ -198,65 +226,65 @@ module.exports = runESMImports().then(() => defineConfig([
typeDetectGlobalObject: false
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
ecmaVersion: 'latest',
sourceType: 'module'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-lang/**/*.js"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-lang/**/*.js'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
ecmaVersion: 'latest',
sourceType: 'module'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-requests/**/*.ts"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-requests/**/*.ts'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parser: require("@typescript-eslint/parser"),
parser: require('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./packages/bruno-requests/tsconfig.json",
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './packages/bruno-requests/tsconfig.json'
}
},
rules: {
"no-undef": "error",
},
'no-undef': 'error'
}
},
{
files: ["packages/bruno-requests/**/*.js"],
ignores: ["**/*.config.js", "**/dist/**/*"],
files: ['packages/bruno-requests/**/*.js'],
ignores: ['**/*.config.js', '**/dist/**/*'],
languageOptions: {
globals: {
...globals.node,
...globals.jest,
...globals.jest
},
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
ecmaVersion: 'latest',
sourceType: 'module'
}
},
rules: {
"no-undef": "error",
},
},
'no-undef': 'error'
}
}
]));

5621
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
"packages/bruno-common",
"packages/bruno-converters",
"packages/bruno-schema",
"packages/bruno-schema-types",
"packages/bruno-query",
"packages/bruno-js",
"packages/bruno-lang",
@@ -22,8 +23,13 @@
"@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",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/jest": "^29.5.11",
"@types/lodash-es": "^4.17.12",
@@ -42,12 +48,13 @@
"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",
@@ -55,12 +62,14 @@
"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",
"build:bruno-converters": "npm run build --workspace=packages/bruno-converters",
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
"build:schema-types": "npm run build --workspace=packages/bruno-schema-types",
"build:electron": "node ./scripts/build-electron.js",
"build:electron:mac": "./scripts/build-electron.sh mac",
"build:electron:win": "./scripts/build-electron.sh win",
@@ -69,6 +78,7 @@
"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",
@@ -89,5 +99,8 @@
"json-schema-typed": "8.0.1"
}
}
},
"dependencies": {
"ajv": "^8.17.1"
}
}

View File

@@ -22,6 +22,7 @@ build
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.log
# local env files
.env.local
@@ -33,4 +34,5 @@ yarn-error.log*
.next/
dist/
.env
.env
storybook-static/

View File

@@ -6,4 +6,4 @@ module.exports = {
}]
],
plugins: ['babel-plugin-styled-components']
};
};

View File

@@ -1,10 +1,10 @@
module.exports = {
rootDir: '.',
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
'^.+\\.[jt]sx?$': 'babel-jest'
},
transformIgnorePatterns: [
"/node_modules/(?!strip-json-comments|nanoid|xml-formatter)/",
'/node_modules/(?!strip-json-comments|nanoid|xml-formatter)/'
],
moduleNameMapper: {
'^assets/(.*)$': '<rootDir>/src/assets/$1',
@@ -22,9 +22,9 @@ module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['@testing-library/jest-dom'],
setupFiles: [
'<rootDir>/jest.setup.js',
'<rootDir>/jest.setup.js'
],
testMatch: [
'<rootDir>/src/**/*.spec.[jt]s?(x)'
]
};
};

View File

@@ -9,7 +9,9 @@
"preview": "rsbuild preview",
"test": "jest",
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
"prettier": "prettier --write \"./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"
},
"dependencies": {
"@fontsource/inter": "^5.0.15",
@@ -21,12 +23,16 @@
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.7.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"classnames": "^2.3.1",
"codemirror": "5.65.2",
"codemirror-graphql": "2.1.1",
"cookie": "0.7.1",
"dompurify": "^3.2.4",
"escape-html": "^1.0.3",
"fast-fuzzy": "^1.12.0",
"fast-json-format": "~0.4.0",
"file": "^0.2.2",
"file-dialog": "^0.0.8",
"file-saver": "^2.0.5",
@@ -35,20 +41,24 @@
"graphiql": "3.7.1",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"hexy": "^0.3.5",
"httpsnippet": "^3.0.9",
"i18next": "24.1.2",
"iconv-lite": "^0.6.3",
"idb": "^7.0.0",
"immer": "^9.0.15",
"js-yaml": "^4.1.0",
"jsesc": "^3.0.2",
"jshint": "^2.13.6",
"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",
"markdown-it": "^13.0.2",
"markdown-it-replace-link": "^1.2.0",
"mime-types": "^3.0.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.47",
"mousetrap": "^1.6.5",
@@ -56,9 +66,10 @@
"path": "^0.12.7",
"pdfjs-dist": "4.4.168",
"platform": "^1.3.6",
"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",
@@ -73,14 +84,17 @@
"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",
"strip-json-comments": "^5.0.1",
"styled-components": "^5.3.3",
"swagger-ui-react": "5.17.12",
"system": "^2.0.1",
"url": "^0.11.3",
"xml-formatter": "^3.5.0",
"xml2js": "^0.6.2",
"yup": "^0.32.11"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
const darkTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'brand': '#546de5',
'text': 'rgb(52 52 52)',
'primary-text': '#ffffff',
'primary-theme': '#1e1e1e',
'secondary-text': '#929292',

View File

@@ -1,6 +1,6 @@
const lightTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'brand': '#546de5',
'text': 'rgb(52 52 52)',
'primary-text': 'rgb(52 52 52)',
'primary-theme': '#ffffff',
'secondary-text': '#929292',

View File

@@ -4,7 +4,7 @@ import { AccordionItem, AccordionHeader, AccordionContent } from './styledWrappe
const AccordionContext = createContext();
const Accordion = ({ children, defaultIndex }) => {
const Accordion = ({ children, defaultIndex, dataTestId }) => {
const [openIndex, setOpenIndex] = useState(defaultIndex);
const toggleItem = (index) => {
@@ -13,7 +13,7 @@ const Accordion = ({ children, defaultIndex }) => {
return (
<AccordionContext.Provider value={{ openIndex, toggleItem }}>
<div>{children}</div>
<div data-testid={dataTestId}>{children}</div>
</AccordionContext.Provider>
);
};

View File

@@ -0,0 +1,129 @@
const yamlPlugin = (cm) => {
cm.defineMode('yaml', function () {
var cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
var keywordRegex = new RegExp('\\b((' + cons.join(')|(') + '))$', 'i');
return {
token: function (stream, state) {
var ch = stream.peek();
var esc = state.escaped;
state.escaped = false;
/* comments */
if (ch == '#' && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) {
stream.skipToEnd();
return 'comment';
}
if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/)) return 'string';
if (state.literal && stream.indentation() > state.keyCol) {
stream.skipToEnd();
return 'string';
} else if (state.literal) {
state.literal = false;
}
if (stream.sol()) {
state.keyCol = 0;
state.pair = false;
state.pairStart = false;
/* document start */
if (stream.match('---')) {
return 'def';
}
/* document end */
if (stream.match('...')) {
return 'def';
}
/* array list item */
if (stream.match(/\s*-\s+/)) {
return 'meta';
}
}
/* inline pairs/lists */
if (stream.match(/^(\{|\}|\[|\])/)) {
if (ch == '{') state.inlinePairs++;
else if (ch == '}') state.inlinePairs--;
else if (ch == '[') state.inlineList++;
else state.inlineList--;
return 'meta';
}
/* list separator */
if (state.inlineList > 0 && !esc && ch == ',') {
stream.next();
return 'meta';
}
/* pairs separator */
if (state.inlinePairs > 0 && !esc && ch == ',') {
state.keyCol = 0;
state.pair = false;
state.pairStart = false;
stream.next();
return 'meta';
}
/* start of value of a pair */
if (state.pairStart) {
/* block literals */
if (stream.match(/^\s*(\||\>)\s*/)) {
state.literal = true;
return 'meta';
}
/* references */
if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) {
return 'variable-2';
}
/* numbers */
if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) {
return 'number';
}
if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) {
return 'number';
}
/* keywords */
if (stream.match(keywordRegex)) {
return 'keyword';
}
}
/* pairs (associative arrays) -> key */
if (
!state.pair
&& stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/)
) {
state.pair = true;
state.keyCol = stream.indentation();
return 'atom';
}
if (state.pair && stream.match(/^:\s*/)) {
state.pairStart = true;
return 'meta';
}
/* nothing found, continue */
state.pairStart = false;
state.escaped = ch == '\\';
stream.next();
return null;
},
startState: function () {
return {
pair: false,
pairStart: false,
keyCol: 0,
inlinePairs: 0,
inlineList: 0,
literal: false,
escaped: false
};
},
lineComment: '#',
fold: 'indent'
};
});
cm.defineMIME('text/x-yaml', 'yaml');
cm.defineMIME('text/yaml', 'yaml');
};
export default yamlPlugin;

View File

@@ -0,0 +1,65 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
height: calc(100vh - 4rem);
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
line-break: anywhere;
}
.CodeMirror-dialog {
overflow: visible;
input {
background: transparent;
border: 1px solid #d3d6db;
outline: none;
border-radius: 0px;
}
}
.CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background: #d2d7db;
}
textarea.cm-editor {
position: relative;
}
// Todo: dark mode temporary fix
// Clean this
.CodeMirror.cm-s-monokai {
.CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background: #444444;
}
}
.cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number {
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom {
color: #569cd6 !important;
}
.cm-variable-valid {
color: ${(props) => props.theme.codemirror.variable.valid};
}
.cm-variable-invalid {
color: ${(props) => props.theme.codemirror.variable.invalid};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2021 GraphQL Contributors.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import StyledWrapper from './StyledWrapper';
import yamlPlugin from './Plugins/Yaml/index';
let CodeMirror;
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');
}
export default class CodeEditor extends React.Component {
constructor(props) {
super(props);
this.cachedValue = props.value || '';
this.variables = {};
this.lintOptions = {
esversion: 11,
expr: true,
asi: true
};
}
componentWillMount() {
switch (this.props.mode) {
case 'yaml':
// YAML linting and hightlighting plugin
yamlPlugin(CodeMirror);
break;
default:
break;
}
}
componentDidMount() {
const editor = (this.editor = CodeMirror(this._node, {
value: this.props.value || '',
lineNumbers: true,
lineWrapping: true,
tabSize: 2,
mode: this.props.mode || 'application/text',
keyMap: 'sublime',
autoCloseBrackets: true,
matchBrackets: true,
showCursorWhenSelecting: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
lint: this.lintOptions,
readOnly: this.props.readOnly,
scrollbarStyle: 'overlay',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
extraKeys: {
'Cmd-S': () => {
if (this.props.onSave) {
this.props.onSave();
}
},
'Ctrl-S': () => {
if (this.props.onSave) {
this.props.onSave();
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent',
'Cmd-H': 'replace',
'Ctrl-H': 'replace',
'Tab': function (cm) {
cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection()
? cm.execCommand('indentMore')
: cm.replaceSelection(' ', 'end');
},
'Shift-Tab': 'indentLess',
'Ctrl-Space': 'autocomplete',
'Cmd-Space': 'autocomplete',
'Ctrl-Y': 'foldAll',
'Cmd-Y': 'foldAll',
'Ctrl-I': 'unfoldAll',
'Cmd-I': 'unfoldAll'
}
}));
if (editor) {
editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false);
editor.on('change', this._onEdit);
}
}
componentDidUpdate(prevProps) {
this.ignoreChangeEvent = true;
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;
}
componentWillUnmount() {
if (this.editor) {
this.editor.off('change', this._onEdit);
this.editor = null;
}
}
render() {
if (this.editor) {
this.editor.refresh();
}
return (
<StyledWrapper
className="h-full w-full graphiql-container"
aria-label="Code Editor"
font={this.props.font}
ref={(node) => {
this._node = node;
}}
/>
);
}
_onEdit = () => {
if (!this.ignoreChangeEvent && this.editor) {
this.editor.setOption('lint', this.editor.getValue().trim().length > 0 ? this.lintOptions : false);
this.cachedValue = this.editor.getValue();
if (this.props.onEdit) {
this.props.onEdit(this.cachedValue);
}
}
};
}

View File

@@ -0,0 +1,51 @@
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from './CodeEditor/index';
import { IconDeviceFloppy } from '@tabler/icons';
import { saveApiSpecToFile } from 'providers/ReduxStore/slices/apiSpec';
import { useState } from 'react';
const FileEditor = ({ apiSpec }) => {
const dispatch = useDispatch();
const { displayedTheme, theme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const [content, setContent] = useState(apiSpec?.raw);
const onEdit = (value) => {
setContent(value);
};
const onSave = () => {
dispatch(saveApiSpecToFile({ uid: apiSpec?.uid, content }));
};
const hasChanges = Boolean(content != apiSpec?.raw);
const editorMode = 'yaml';
return (
<div className="flex flex-grow relative">
<CodeEditor
theme={displayedTheme}
value={content}
onEdit={onEdit}
onSave={onSave}
mode={editorMode}
font={get(preferences, 'font.codeFont', 'default')}
/>
<IconDeviceFloppy
onClick={onSave}
color={hasChanges ? theme.draftColor : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
className={`absolute right-0 top-0 m-4 ${
hasChanges ? 'cursor-pointer oapcity-100' : 'cursor-default opacity-50'
}`}
/>
</div>
);
};
export default FileEditor;

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.swagger-root {
height: calc(100vh - 4rem);
border: solid 1px ${(props) => props.theme.codemirror.border};
&.dark {
.swagger-ui {
filter: invert(88%) hue-rotate(180deg);
}
.swagger-ui .microlight {
filter: invert(100%) hue-rotate(180deg);
}
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,19 @@
import SwaggerUI from 'swagger-ui-react';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const Swagger = ({ string }) => {
const { displayedTheme } = useTheme();
console.log('string', string);
return (
<StyledWrapper>
<div className={`swagger-root w-full overflow-y-scroll ${displayedTheme}`}>
<SwaggerUI spec={string} />
</div>
</StyledWrapper>
);
};
export default Swagger;

View File

@@ -0,0 +1,22 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.menu-icon {
cursor: pointer;
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
}
div.dropdown-item.menu-item {
color: ${(props) => props.theme.colors.text.danger};
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger};
color: white;
}
}
.react-tooltip {
z-index: 10;
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,97 @@
import React, { forwardRef, useRef } from 'react';
import find from 'lodash/find';
import { useSelector, useDispatch } from 'react-redux';
import { IconFileCode, IconDots } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
import FileEditor from './FileEditor';
import Dropdown from 'components/Dropdown';
import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec';
import { useState } from 'react';
import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec';
import { Suspense } from 'react';
import Swagger from './Renderers/Swagger';
import toast from 'react-hot-toast';
const ApiSpecPanel = () => {
const dispatch = useDispatch();
const [createApiSpecModalOpen, setCreateApiSpecModalOpen] = useState(false);
const { apiSpecs, activeApiSpecUid } = useSelector((state) => state.apiSpec);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
let apiSpec = find(apiSpecs, (c) => c.uid === activeApiSpecUid);
const { filename, pathname, raw, uid } = apiSpec || {};
if (!uid) {
return <div className="p-4 opacity-50">API Spec not found!</div>;
}
const MenuIcon = forwardRef((props, ref) => {
return (
<div ref={ref}>
<IconDots size={22} />
</div>
);
});
const handleOpenApiSpec = () => {
dispatch(openApiSpec()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the API spec')
);
};
return (
<StyledWrapper className="flex flex-col flex-grow relative">
{createApiSpecModalOpen ? <CreateApiSpec onClose={() => setCreateApiSpecModalOpen(false)} /> : null}
<div className="p-3 mb-2 w-full flex flex-row justify-between grid grid-cols-3">
<div className="flex flex-row justify-start gap-x-4 col-span-1">
<div className="flex w-fit items-center cursor-pointer">
<IconFileCode size={18} strokeWidth={1.5} />
<span className="ml-2 mr-4 font-semibold">API Designer</span>
</div>
</div>
<div className="w-full col-span-1 flex justify-center" title={pathname}>
{filename}
</div>
<div className="menu-icon pr-2 col-span-1 flex justify-end">
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
setCreateApiSpecModalOpen(true);
}}
>
Create API Spec
</div>
<div
className="dropdown-item"
onClick={(e) => {
dropdownTippyRef.current.hide();
handleOpenApiSpec();
}}
>
Open API Spec
</div>
</Dropdown>
</div>
</div>
<section className="main flex flex-grow px-4 relative">
<div className="w-full grid grid-cols-2">
<div className="col-span-1">
<FileEditor apiSpec={apiSpec} />
</div>
<div className="col-span-1">
<Suspense fallback="">
<Swagger string={raw} />
</Suspense>
</div>
</div>
</section>
</StyledWrapper>
);
};
export default ApiSpecPanel;

View File

@@ -0,0 +1,251 @@
import styled from 'styled-components';
const Wrapper = styled.div`
height: 36px;
display: flex;
align-items: center;
background: ${(props) => props.theme.sidebar.bg};
-webkit-app-region: drag;
user-select: none;
.titlebar-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
padding: 0 12px;
padding-left: 70px; /* Space for macOS window controls */
transition: padding-left 0.15s ease;
}
/* When in full screen, no traffic lights so reduce padding */
&.fullscreen .titlebar-content {
padding-left: 6px;
}
/* Remove drag region from interactive elements */
.workspace-name-container,
.dropdown-item,
.home-button,
.dropdown,
button {
-webkit-app-region: no-drag;
}
/* Left section */
.titlebar-left {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 10px;
-webkit-app-region: no-drag;
}
/* When in full screen, no traffic lights so remove margin-left */
&.fullscreen .titlebar-left {
margin-left: 0px;
}
/* Workspace Name Dropdown Trigger */
.workspace-name-container {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
}
.workspace-name {
font-size: 13px;
font-weight: 500;
color: ${(props) => props.theme.sidebar.color};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.chevron-icon {
flex-shrink: 0;
color: ${(props) => props.theme.sidebar.muted};
transition: transform 0.2s ease;
}
}
/* Center section - Bruno branding */
.titlebar-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 6px;
pointer-events: none;
.bruno-text {
font-size: 13px;
font-weight: 600;
color: ${(props) => props.theme.text};
letter-spacing: 0.5px;
}
}
/* Right section */
.titlebar-right {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
-webkit-app-region: no-drag;
}
/* App action buttons container */
.titlebar-actions {
display: flex;
align-items: center;
}
/* Workspace Dropdown Styles */
.workspace-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 10px !important;
margin: 0 !important;
&.active {
.check-icon {
opacity: 1;
}
}
&:hover {
.pin-btn:not(.pinned) {
opacity: 1;
}
}
.workspace-name {
flex: 1;
min-width: 0;
font-size: 13px;
font-weight: 400;
color: ${(props) => props.theme.dropdown.color};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.workspace-actions {
display: flex;
align-items: center;
gap: 4px;
margin-left: 8px;
flex-shrink: 0;
pointer-events: none;
> * {
pointer-events: auto;
}
}
.check-icon {
color: ${(props) => props.theme.workspace?.accent || props.theme.colors?.text?.yellow};
flex-shrink: 0;
}
.pin-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
color: ${(props) => props.theme.dropdown.mutedText};
transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
opacity: 0;
&.pinned {
opacity: 1;
}
&:hover {
background: ${(props) => props.theme.dropdown.hoverBg};
color: ${(props) => props.theme.dropdown.mutedText};
}
}
}
/* Adjust for non-macOS platforms */
&:not(.os-mac) .titlebar-content {
padding-left: 12px;
}
/* Windows-specific styles */
&.os-windows .titlebar-content {
padding-right: 0px;
padding-left: 0px;
}
&.os-windows .titlebar-left {
margin-left: 6px;
}
&.os-linux .titlebar-content {
padding-right: 0px;
padding-left: 0px;
}
&.os-linux .titlebar-left {
margin-left: 6px;
}
/* Custom window control buttons for Windows - always interactive, above modal overlay */
.window-controls {
display: flex;
align-items: stretch;
height: 36px;
margin-left: 8px;
position: relative;
z-index: 1000;
}
.window-control-btn {
display: flex;
align-items: center;
justify-content: center;
width: 46px;
height: 100%;
border: none;
background: transparent;
color: ${(props) => props.theme.text};
cursor: pointer;
transition: background-color 0.1s ease;
-webkit-app-region: no-drag;
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
}
&:active {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
}
&.close:hover {
background: #e81123;
color: white;
}
}
`;
export default Wrapper;

View File

@@ -0,0 +1,330 @@
import React from 'react';
import { IconCheck, IconChevronDown, IconFolder, IconHome, IconPin, IconPinned, IconPlus, IconDownload, IconSettings, IconMinus, IconSquare, IconX, IconCopy } from '@tabler/icons';
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
import { savePreferences, showHomePage, showManageWorkspacePage, toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
import { closeConsole, openConsole } from 'providers/ReduxStore/slices/logs';
import { openWorkspaceDialog, switchWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces';
import Bruno from 'components/Bruno';
import MenuDropdown from 'ui/MenuDropdown';
import ActionIcon from 'ui/ActionIcon';
import IconSidebarToggle from 'components/Icons/IconSidebarToggle';
import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
import ImportWorkspace from 'components/WorkspaceSidebar/ImportWorkspace';
import IconBottombarToggle from 'components/Icons/IconBottombarToggle/index';
import StyledWrapper from './StyledWrapper';
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';
if (isWindowsOS()) return 'os-windows';
if (isLinuxOS()) return 'os-linux';
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);
const [isMaximized, setIsMaximized] = useState(false);
const osClass = getOsClass();
const isWindows = osClass === 'os-windows';
const isLinux = osClass === 'os-linux';
const showWindowControls = isWindows || isLinux;
// Listen for fullscreen changes
useEffect(() => {
const { ipcRenderer } = window;
if (!ipcRenderer) return;
const removeEnterFullScreenListener = ipcRenderer.on('main:enter-full-screen', () => {
setIsFullScreen(true);
});
const removeLeaveFullScreenListener = ipcRenderer.on('main:leave-full-screen', () => {
setIsFullScreen(false);
});
return () => {
removeEnterFullScreenListener();
removeLeaveFullScreenListener();
};
}, []);
useEffect(() => {
if (!showWindowControls) return;
const { ipcRenderer } = window;
if (!ipcRenderer) return;
ipcRenderer.invoke('renderer:window-is-maximized')
.then((maximized) => {
setIsMaximized(maximized);
})
.catch((error) => {
console.error('Error getting initial maximized state:', error);
});
const removeMaximizedListener = ipcRenderer.on('main:window-maximized', () => {
setIsMaximized(true);
});
const removeUnmaximizedListener = ipcRenderer.on('main:window-unmaximized', () => {
setIsMaximized(false);
});
return () => {
removeMaximizedListener();
removeUnmaximizedListener();
};
}, [showWindowControls]);
const handleMinimize = useCallback(() => {
window.ipcRenderer?.send('renderer:window-minimize');
}, []);
const handleMaximize = useCallback(() => {
window.ipcRenderer?.send('renderer:window-maximize');
// State will be updated via IPC events from main process (main:window-maximized/main:window-unmaximized)
}, []);
const handleClose = useCallback(() => {
window.ipcRenderer?.send('renderer:window-close');
}, []);
// Get workspace info
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const preferences = useSelector((state) => state.app.preferences);
const sidebarCollapsed = useSelector((state) => state.app.sidebarCollapsed);
const isConsoleOpen = useSelector((state) => state.logs.isConsoleOpen);
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
// Sort workspaces according to preferences
const sortedWorkspaces = useMemo(() => {
return sortWorkspaces(workspaces, preferences);
}, [workspaces, preferences]);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
const [importWorkspaceModalOpen, setImportWorkspaceModalOpen] = useState(false);
const WorkspaceName = forwardRef((props, ref) => {
return (
<div ref={ref} className="workspace-name-container" {...props}>
<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>
);
});
const handleHomeClick = () => {
dispatch(showHomePage());
};
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
toast.success(`Switched to ${getWorkspaceDisplayName(workspaces.find((w) => w.uid === workspaceUid)?.name)}`);
};
const handleOpenWorkspace = async () => {
try {
await dispatch(openWorkspaceDialog());
toast.success('Workspace opened successfully');
} catch (error) {
toast.error(error.message || 'Failed to open workspace');
}
};
const handleCreateWorkspace = () => {
setCreateWorkspaceModalOpen(true);
};
const handleManageWorkspaces = () => {
dispatch(showManageWorkspacePage());
};
const handleImportWorkspace = () => {
setImportWorkspaceModalOpen(true);
};
const handlePinWorkspace = useCallback((workspaceUid, e) => {
e.preventDefault();
e.stopPropagation();
const newPreferences = toggleWorkspacePin(workspaceUid, preferences);
dispatch(savePreferences(newPreferences));
}, [dispatch, preferences]);
const handleToggleSidebar = () => {
dispatch(toggleSidebarCollapse());
};
const handleToggleDevtools = () => {
if (isConsoleOpen) {
dispatch(closeConsole());
} else {
dispatch(openConsole());
}
};
// Build workspace menu items
const workspaceMenuItems = useMemo(() => {
const items = sortedWorkspaces.map((workspace) => {
const isActive = workspace.uid === activeWorkspaceUid;
const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
return {
id: workspace.uid,
label: getWorkspaceDisplayName(workspace.name),
onClick: () => handleWorkspaceSwitch(workspace.uid),
className: `workspace-item ${isActive ? 'active' : ''}`,
rightSection: (
<div className="workspace-actions">
{workspace.type !== 'default' && (
<ActionIcon
className={`pin-btn ${isPinned ? 'pinned' : ''}`}
onClick={(e) => handlePinWorkspace(workspace.uid, e)}
label={isPinned ? 'Unpin workspace' : 'Pin workspace'}
size="sm"
>
{isPinned ? <IconPinned size={14} stroke={1.5} /> : <IconPin size={14} stroke={1.5} />}
</ActionIcon>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
</div>
)
};
});
// Add label and action items
items.push(
{ type: 'label', label: 'Workspaces' },
{
id: 'create-workspace',
leftSection: IconPlus,
label: 'Create workspace',
onClick: handleCreateWorkspace
},
{
id: 'open-workspace',
leftSection: IconFolder,
label: 'Open workspace',
onClick: handleOpenWorkspace
},
{
id: 'import-workspace',
leftSection: IconDownload,
label: 'Import workspace',
onClick: handleImportWorkspace
},
{
id: 'manage-workspaces',
leftSection: IconSettings,
label: 'Manage workspaces',
onClick: handleManageWorkspaces
}
);
return items;
}, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]);
return (
<StyledWrapper className={`app-titlebar ${osClass} ${isFullScreen ? 'fullscreen' : ''}`}>
{createWorkspaceModalOpen && (
<CreateWorkspace onClose={() => setCreateWorkspaceModalOpen(false)} />
)}
{importWorkspaceModalOpen && (
<ImportWorkspace onClose={() => setImportWorkspaceModalOpen(false)} />
)}
<div className="titlebar-content">
{/* Left section: Home + Workspace */}
<div className="titlebar-left">
<ActionIcon onClick={handleHomeClick} label="Home" size="lg" className="home-button">
<IconHome size={16} stroke={1.5} />
</ActionIcon>
{/* Workspace Dropdown */}
<MenuDropdown
data-testid="workspace-menu"
items={workspaceMenuItems}
placement="bottom-start"
selectedItemId={activeWorkspaceUid}
>
<WorkspaceName />
</MenuDropdown>
</div>
{/* Center section: Bruno logo + text */}
<div className="titlebar-center">
<Bruno width={18} />
<span className="bruno-text">Bruno</span>
</div>
{/* Right section: Action buttons */}
<div className="titlebar-right">
<div className="titlebar-actions">
{/* Toggle sidebar */}
<ActionIcon
onClick={handleToggleSidebar}
label={sidebarCollapsed ? 'Show sidebar' : 'Hide sidebar'}
size="lg"
data-testid="toggle-sidebar-button"
>
<IconSidebarToggle collapsed={sidebarCollapsed} size={16} strokeWidth={1.5} />
</ActionIcon>
{/* Toggle devtools */}
<ActionIcon
onClick={handleToggleDevtools}
label={isConsoleOpen ? 'Hide devtools' : 'Show devtools'}
size="lg"
data-testid="toggle-devtools-button"
>
<IconBottombarToggle collapsed={!isConsoleOpen} size={16} strokeWidth={1.5} />
</ActionIcon>
<ResponseLayoutToggle />
</div>
{showWindowControls && (
<div className="window-controls">
<button
className="window-control-btn minimize"
onClick={handleMinimize}
aria-label="Minimize"
>
<IconMinus size={16} stroke={1} />
</button>
<button
className="window-control-btn maximize"
onClick={handleMaximize}
aria-label={isMaximized ? 'Restore' : 'Maximize'}
>
{isMaximized ? <IconCopy size={14} stroke={1} /> : <IconSquare size={14} stroke={1} />}
</button>
<button
className="window-control-btn close"
onClick={handleClose}
aria-label="Close"
>
<IconX size={16} stroke={1} />
</button>
</div>
)}
</div>
</div>
</StyledWrapper>
);
};
export default AppTitleBar;

View File

@@ -0,0 +1,38 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
font-size: ${(props) => props.theme.font.size.base};
.body-mode-selector {
background: transparent;
border-radius: 3px;
.dropdown-item {
padding: 0.2rem 0.6rem !important;
padding-left: 1.5rem !important;
display: flex;
align-items: center;
}
.label-item {
padding: 0.2rem 0.6rem !important;
}
.selected-body-mode {
color: ${(props) => props.theme.primary.text};
}
.dropdown-icon {
display: flex;
align-items: center;
margin-right: 0.5rem;
}
}
.caret {
color: ${(props) => props.theme.colors.text.muted};
fill: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,76 @@
import React, { useMemo } from 'react';
import { IconCaretDown, IconForms, IconBraces, IconCode, IconFileText, IconDatabase, IconFile, IconX } from '@tabler/icons';
import MenuDropdown from 'ui/MenuDropdown';
import { humanizeRequestBodyMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const DEFAULT_MODES = [
{
name: 'Form',
options: [
{ id: 'multipartForm', label: 'Multipart Form', leftSection: IconForms },
{ id: 'formUrlEncoded', label: 'Form URL Encoded', leftSection: IconForms }
]
},
{
name: 'Raw',
options: [
{ id: 'json', label: 'JSON', leftSection: IconBraces },
{ id: 'xml', label: 'XML', leftSection: IconCode },
{ id: 'text', label: 'TEXT', leftSection: IconFileText },
{ id: 'sparql', label: 'SPARQL', leftSection: IconDatabase }
]
},
{
name: 'Other',
options: [
{ id: 'file', label: 'File / Binary', leftSection: IconFile },
{ id: 'none', label: 'No Body', leftSection: IconX }
]
}
];
const BodyModeSelector = ({
currentMode,
onModeChange,
modes = DEFAULT_MODES,
disabled = false,
className = '',
wrapperClassName = '',
placement = 'bottom-end'
}) => {
// Add onClick handlers to mode options
const menuItems = useMemo(() => {
return modes.map((group) => ({
...group,
options: group.options.map((option) => ({
...option,
onClick: () => onModeChange(option.id)
}))
}));
}, [modes, onModeChange]);
return (
<StyledWrapper className={wrapperClassName}>
<div className={`inline-flex items-center body-mode-selector ${disabled ? 'cursor-default' : 'cursor-pointer'}`}>
<MenuDropdown
items={menuItems}
placement={placement}
disabled={disabled}
className={className}
selectedItemId={currentMode}
showGroupDividers={false}
groupStyle="select"
>
<div className="flex items-center justify-center pl-3 py-1 select-none selected-body-mode">
{humanizeRequestBodyMode(currentMode)}
{' '}
<IconCaretDown className="caret ml-2" size={14} strokeWidth={2} />
</div>
</MenuDropdown>
</div>
</StyledWrapper>
);
};
export default BodyModeSelector;

View File

@@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper';
const BrunoSupport = ({ onClose }) => {
return (
<StyledWrapper>
<Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
<Modal size="sm" title="Support" handleCancel={onClose} hideFooter={true}>
<div className="collection-options">
<div className="mt-2">
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">

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

@@ -0,0 +1,79 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.checkbox-container {
width: 1rem;
height: 1rem;
display: flex;
justify-content: center;
align-items: center;
position: relative;
cursor: pointer;
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
.checkbox-checkmark {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
visibility: ${(props) => props.checked ? 'visible' : 'hidden'};
pointer-events: none;
}
.checkbox-input {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: 1rem;
height: 1rem;
border: 2px solid ${(props) => {
if (props.checked && props.disabled) {
return props.theme.colors.text.muted;
}
if (props.checked && !props.disabled) {
return props.theme.colors.text.yellow;
}
return props.theme.colors.text.muted;
}};
border-radius: 4px;
background-color: ${(props) => {
if (props.checked && !props.disabled) {
return props.theme.colors.text.yellow;
}
if (props.checked && props.disabled) {
return props.theme.colors.text.muted;
}
return 'transparent';
}};
cursor: pointer;
position: relative;
transition: all 0.2s ease;
outline: none;
box-shadow: none;
&:hover:not(:disabled) {
opacity: 0.8;
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
&:focus {
outline: none;
box-shadow: 0 0 0 2px ${(props) => props.theme.colors.text.yellow}40;
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,45 @@
import React from 'react';
import StyledWrapper from './StyledWrapper';
import IconCheckMark from 'components/Icons/IconCheckMark';
import { useTheme } from 'providers/Theme';
const Checkbox = ({
checked = false,
disabled = false,
onChange,
className = '',
id,
name,
value,
dataTestId = 'checkbox'
}) => {
const { theme } = useTheme();
const handleChange = (e) => {
if (!disabled && onChange) {
onChange(e);
}
};
return (
<StyledWrapper checked={checked} disabled={disabled} className={className}>
<div className="checkbox-container">
<input
type="checkbox"
id={id}
name={name}
value={value}
checked={checked}
disabled={disabled}
onChange={handleChange}
className="checkbox-input"
data-testid={dataTestId}
/>
<IconCheckMark className="checkbox-checkmark" color={theme.examples.checkbox.color} size={14} />
</div>
</StyledWrapper>
);
};
export default Checkbox;

View File

@@ -1,6 +1,12 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
&.read-only {
div.CodeMirror .CodeMirror-cursor {
display: none !important;
}
}
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
@@ -12,6 +18,33 @@ const StyledWrapper = styled.div`
flex-direction: column-reverse;
}
.CodeMirror-placeholder {
color: ${(props) => props.theme.text} !important;
opacity: 0.5 !important;
}
.CodeMirror-linenumber {
text-align: left !important;
padding-left: 3px !important;
}
/* Override default lint highlight background when emphasizing the gutter */
.CodeMirror-lint-line-error,
.CodeMirror-lint-line-warning {
background: none !important;
}
/* Style line numbers when there's a lint issue */
.CodeMirror-lint-line-error .CodeMirror-linenumber {
color: ${(props) => props.theme.colors.text.danger} !important;
text-decoration: underline;
}
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
color: ${(props) => props.theme.colors.text.warning} !important;
text-decoration: underline;
}
/* Removes the glow outline around the folded json */
.CodeMirror-foldmarker {
text-shadow: none;
@@ -67,41 +100,54 @@ const StyledWrapper = styled.div`
}
}
.cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number {
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom {
color: #569cd6 !important;
.cm-s-default, .cm-s-monokai {
span.cm-def {
color: ${(props) => props.theme.codemirror.tokens.definition} !important;
}
span.cm-property {
color: ${(props) => props.theme.codemirror.tokens.property} !important;
}
span.cm-string {
color: ${(props) => props.theme.codemirror.tokens.string} !important;
}
span.cm-number {
color: ${(props) => props.theme.codemirror.tokens.number} !important;
}
span.cm-atom {
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
}
span.cm-variable, span.cm-variable-2 {
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
}
span.cm-keyword {
color: ${(props) => props.theme.codemirror.tokens.keyword} !important;
}
span.cm-comment {
color: ${(props) => props.theme.codemirror.tokens.comment} !important;
}
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: green;
color: ${(props) => props.theme.codemirror.variable.valid} !important;
}
.cm-variable-invalid {
color: red;
color: ${(props) => props.theme.codemirror.variable.invalid} !important;
}
.CodeMirror-search-hint {
display: inline;
}
.cm-s-default span.cm-property {
color: #1f61a0 !important;
}
.cm-s-default span.cm-variable {
color: #397d13 !important;
}
//matching bracket fix
.CodeMirror-matchingbracket {
@@ -120,6 +166,31 @@ const StyledWrapper = styled.div`
.cm-search-current {
background: rgba(255, 193, 7, 0.4);
}
.lint-error-tooltip {
position: fixed;
z-index: 10000;
background: ${(props) => props.theme.codemirror.bg};
border-radius: ${(props) => props.theme.border.radius.base};
padding: 8px 12px;
max-width: 400px;
box-shadow: ${(props) => props.theme.shadow.sm};
font-size: ${(props) => props.theme.font.size.xs};
line-height: 1.5;
pointer-events: none;
.lint-tooltip-message {
padding: 2px 0;
}
.lint-tooltip-message.error {
color: ${(props) => props.theme.colors.text.danger};
}
.lint-tooltip-message.warning {
color: ${(props) => props.theme.colors.text.warning};
}
}
`;
export default StyledWrapper;

View File

@@ -8,13 +8,15 @@
import React 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';
import stripJsonComments from 'strip-json-comments';
import { getAllVariables } from 'utils/collections';
import CustomSearch from './CustomSearch';
import { setupLinkAware } from 'utils/codemirror/linkAware';
import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors';
import CodeMirrorSearch from 'components/CodeMirrorSearch';
const CodeMirror = require('codemirror');
window.jsonlint = jsonlint;
@@ -36,7 +38,8 @@ export default class CodeEditor extends React.Component {
this.lintOptions = {
esversion: 11,
expr: true,
asi: true
asi: true,
highlightLines: true
};
this.state = {
@@ -49,19 +52,22 @@ export default class CodeEditor extends React.Component {
const editor = (this.editor = CodeMirror(this._node, {
value: this.props.value || '',
placeholder: '...',
lineNumbers: true,
lineWrapping: this.props.enableLineWrapping ?? true,
tabSize: TAB_SIZE,
mode: this.props.mode || 'application/ld+json',
brunoVarInfo: {
variables
},
brunoVarInfo: this.props.enableBrunoVarInfo !== false ? {
variables,
collection: this.props.collection,
item: this.props.item
} : false,
keyMap: 'sublime',
autoCloseBrackets: true,
matchBrackets: true,
showCursorWhenSelecting: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
lint: this.lintOptions,
readOnly: this.props.readOnly,
scrollbarStyle: 'overlay',
@@ -99,14 +105,18 @@ export default class CodeEditor extends React.Component {
},
'Cmd-H': 'replace',
'Ctrl-H': 'replace',
Tab: function (cm) {
'Tab': function (cm) {
cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection()
? cm.execCommand('indentMore')
: 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',
@@ -145,7 +155,7 @@ export default class CodeEditor extends React.Component {
} else if (this.props.mode == 'application/xml') {
var doc = new DOMParser();
try {
//add header element and remove prefix namespaces for DOMParser
// add header element and remove prefix namespaces for DOMParser
var dcm = doc.parseFromString(
'<a> ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '</a>',
'application/xml'
@@ -182,16 +192,15 @@ export default class CodeEditor extends React.Component {
}
return found;
});
if (editor) {
editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false);
editor.on('change', this._onEdit);
editor.on('scroll', this.onScroll);
editor.scrollTo(null, this.props.initialScroll);
this.addOverlay();
const getAllVariablesHandler = () => getAllVariables(this.props.collection, this.props.item);
// Setup AutoComplete Helper for all modes
const autoCompleteOptions = {
showHintsFor: this.props.showHintsFor,
@@ -202,6 +211,11 @@ export default class CodeEditor extends React.Component {
editor,
autoCompleteOptions
);
setupLinkAware(editor);
// Setup lint error tooltip on line number hover
this.cleanupLintErrorTooltip = setupLintErrorTooltip(editor);
}
}
@@ -218,8 +232,10 @@ 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);
}
if (this.editor) {
@@ -227,6 +243,16 @@ export default class CodeEditor extends React.Component {
if (!isEqual(variables, this.variables)) {
this.addOverlay();
}
// Update collection and item when they change
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
if (!isEqual(this.props.collection, this.editor.options.brunoVarInfo.collection)) {
this.editor.options.brunoVarInfo.collection = this.props.collection;
}
if (!isEqual(this.props.item, this.editor.options.brunoVarInfo.item)) {
this.editor.options.brunoVarInfo.item = this.props.item;
}
}
}
if (this.props.theme !== prevProps.theme && this.editor) {
@@ -245,13 +271,28 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('mode', this.props.mode);
}
if (this.props.readOnly !== prevProps.readOnly && this.editor) {
this.editor.setOption('readOnly', this.props.readOnly);
}
this.ignoreChangeEvent = false;
}
componentWillUnmount() {
if (this.editor) {
if (this.props.onScroll) {
this.props.onScroll(this.editor);
}
this.editor?._destroyLinkAware?.();
this.editor.off('change', this._onEdit);
this.editor.off('scroll', this.onScroll);
// Clean up lint error tooltip
this.cleanupLintErrorTooltip?.();
const wrapper = this.editor.getWrapperElement();
wrapper?.parentNode?.removeChild(wrapper);
this.editor = null;
}
}
@@ -262,12 +303,12 @@ export default class CodeEditor extends React.Component {
}
return (
<StyledWrapper
className="h-full w-full flex flex-col relative graphiql-container"
className={`h-full w-full flex flex-col relative graphiql-container ${this.props.readOnly ? 'read-only' : ''}`}
aria-label="Code Editor"
font={this.props.font}
fontSize={this.props.fontSize}
>
<CustomSearch
<CodeMirrorSearch
visible={this.state.searchBarVisible}
editor={this.editor}
onClose={() => this.setState({ searchBarVisible: false })}
@@ -286,12 +327,15 @@ export default class CodeEditor extends React.Component {
let variables = getAllVariables(this.props.collection, this.props.item);
this.variables = variables;
// Update brunoVarInfo with latest variables
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
this.editor.options.brunoVarInfo.variables = variables;
}
defineCodeMirrorBrunoVariablesMode(variables, mode, false, this.props.enableVariableHighlighting);
this.editor.setOption('mode', 'brunovariables');
};
onScroll = (event) => this.props.onScroll?.(event);
_onEdit = () => {
if (!this.ignoreChangeEvent && this.editor) {
this.editor.setOption('lint', this.editor.getValue().trim().length > 0 ? this.lintOptions : false);

View File

@@ -10,10 +10,10 @@ jest.mock('codemirror', () => {
const MOCK_THEME = {
codemirror: {
bg: "#1e1e1e",
border: "#333",
bg: '#1e1e1e',
border: '#333'
},
textLink: "#007acc",
textLink: '#007acc'
};
const setupEditorState = (editor, { value, cursorPosition }) => {
@@ -27,8 +27,8 @@ const setupEditorState = (editor, { value, cursorPosition }) => {
});
editor.state = {
completionActive: null,
}
completionActive: null
};
};
const setupEditorWithRef = () => {
@@ -47,5 +47,5 @@ describe('CodeEditor', () => {
jest.resetModules();
});
it("add CodeEditor related tests here", () => {});
});
it('add CodeEditor related tests here', () => {});
});

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 {
@@ -27,7 +28,7 @@ const StyledWrapper = styled.div`
border: none;
outline: none;
padding: 1px 2px;
font-size: 13px;
font-size: ${(props) => props.theme.font.size.base};
margin: 0 1px;
height: 28px;
}
@@ -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;
@@ -50,31 +51,18 @@ const StyledWrapper = styled.div`
.searchbar-result-count {
min-width: 28px;
text-align: center;
font-size: 11px;
color: #aaa;
font-size: ${(props) => props.theme.font.size.xs};
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: 13px;
font-size: ${(props) => props.theme.font.size.base};
padding: 1px 2px;
min-width: 80px;
}
@@ -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,5 +1,4 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import debounce from 'lodash/debounce';
import { IconRegex, IconArrowUp, IconArrowDown, IconX, IconLetterCase, IconLetterW } from '@tabler/icons';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
@@ -9,7 +8,7 @@ function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');
}
const CustomSearch = ({ visible, editor, onClose }) => {
const CodeMirrorSearch = ({ visible, editor, onClose }) => {
const [searchText, setSearchText] = useState('');
const [regex, setRegex] = useState(false);
const [caseSensitive, setCaseSensitive] = useState(false);
@@ -167,7 +166,7 @@ const CustomSearch = ({ visible, editor, onClose }) => {
return (
<StyledWrapper>
<div className="bruno-search-bar compact">
<div className="bruno-search-bar">
<input
autoFocus
type="text"
@@ -199,4 +198,4 @@ const CustomSearch = ({ visible, editor, onClose }) => {
);
};
export default CustomSearch;
export default CodeMirrorSearch;

View File

@@ -2,7 +2,8 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
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

@@ -6,7 +6,7 @@ import Dropdown from 'components/Dropdown';
import { useTheme } from 'providers/Theme';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
@@ -16,9 +16,9 @@ const ApiKeyAuth = ({ collection }) => {
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const apikeyAuth = get(collection, 'root.request.auth.apikey', {});
const apikeyAuth = collection.draft?.root ? get(collection, 'draft.root.request.auth.apikey', {}) : get(collection, 'root.request.auth.apikey', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const Icon = forwardRef((props, ref) => {
return (
@@ -43,43 +43,45 @@ const ApiKeyAuth = ({ collection }) => {
};
useEffect(() => {
!apikeyAuth?.placement &&
dispatch(
updateCollectionAuth({
mode: 'apikey',
collectionUid: collection.uid,
content: {
placement: 'header'
}
})
);
!apikeyAuth?.placement
&& dispatch(
updateCollectionAuth({
mode: 'apikey',
collectionUid: collection.uid,
content: {
placement: 'header'
}
})
);
}, [apikeyAuth]);
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

@@ -1,27 +1,19 @@
import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.8125rem;
font-size: ${(props) => props.theme.font.size.base};
.auth-mode-selector {
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.colors.text.yellow};
}
color: ${(props) => props.theme.primary.text};
.dropdown-item {
padding: 0.2rem 0.6rem !important;
.caret {
color: rgb(140, 140, 140);
fill: rgb(140, 140, 140);
}
}
.label-item {
padding: 0.2rem 0.6rem !important;
}
}
.caret {
color: rgb(140, 140, 140);
fill: rgb(140 140 140);
}
`;

View File

@@ -1,7 +1,7 @@
import React, { useRef, forwardRef } from 'react';
import React, { useMemo, useCallback } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import MenuDropdown from 'ui/MenuDropdown';
import { useDispatch } from 'react-redux';
import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestAuthMode } from 'utils/collections';
@@ -9,113 +9,77 @@ import StyledWrapper from './StyledWrapper';
const AuthMode = ({ collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = get(collection, 'root.request.auth.mode');
const authMode = collection.draft?.root ? get(collection, 'draft.root.request.auth.mode') : get(collection, 'root.request.auth.mode');
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onModeChange = (value) => {
const onModeChange = useCallback((value) => {
dispatch(
updateCollectionAuthMode({
collectionUid: collection.uid,
mode: value
})
);
};
}, [dispatch, collection.uid]);
const menuItems = useMemo(() => [
{
id: 'awsv4',
label: 'AWS Sig v4',
onClick: () => onModeChange('awsv4')
},
{
id: 'basic',
label: 'Basic Auth',
onClick: () => onModeChange('basic')
},
{
id: 'wsse',
label: 'WSSE Auth',
onClick: () => onModeChange('wsse')
},
{
id: 'bearer',
label: 'Bearer Token',
onClick: () => onModeChange('bearer')
},
{
id: 'digest',
label: 'Digest Auth',
onClick: () => onModeChange('digest')
},
{
id: 'ntlm',
label: 'NTLM Auth',
onClick: () => onModeChange('ntlm')
},
{
id: 'oauth2',
label: 'OAuth 2.0',
onClick: () => onModeChange('oauth2')
},
{
id: 'apikey',
label: 'API Key',
onClick: () => onModeChange('apikey')
},
{
id: 'none',
label: 'No Auth',
onClick: () => onModeChange('none')
}
], [onModeChange]);
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('awsv4');
}}
>
AWS Sig v4
<MenuDropdown
items={menuItems}
placement="bottom-end"
selectedItemId={authMode}
>
<div className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('wsse');
}}
>
WSSE Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('digest');
}}
>
Digest Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('ntlm');
}}
>
NTLM Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('oauth2');
}}
>
OAuth 2.0
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('apikey');
}}
>
API Key
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('none');
}}
>
No Auth
</div>
</Dropdown>
</MenuDropdown>
</div>
</StyledWrapper>
);

View File

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

View File

@@ -6,18 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const AwsV4Auth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const awsv4Auth = get(collection, 'root.request.auth.awsv4', {});
const awsv4Auth = collection.draft?.root ? get(collection, 'draft.root.request.auth.awsv4', {}) : get(collection, 'root.request.auth.awsv4', {});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(awsv4Auth?.secretAccessKey);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleAccessKeyIdChange = (accessKeyId) => {
dispatch(
@@ -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: 0.8125rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -6,18 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BasicAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const basicAuth = get(collection, 'root.request.auth.basic', {});
const basicAuth = collection.draft?.root ? get(collection, 'draft.root.request.auth.basic', {}) : get(collection, 'root.request.auth.basic', {});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(basicAuth?.password);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
@@ -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: 0.8125rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -6,18 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BearerAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const bearerToken = get(collection, 'root.request.auth.bearer.token', '');
const bearerToken = collection.draft?.root ? get(collection, 'draft.root.request.auth.bearer.token', '') : get(collection, 'root.request.auth.bearer.token', '');
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(bearerToken);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleTokenChange = (token) => {
dispatch(
@@ -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: 0.8125rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -6,18 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const DigestAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const digestAuth = get(collection, 'root.request.auth.digest', {});
const digestAuth = collection.draft?.root ? get(collection, 'draft.root.request.auth.digest', {}) : get(collection, 'root.request.auth.digest', {});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(digestAuth?.password);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
@@ -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: 0.8125rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.single-line-editor-wrapper {

View File

@@ -6,25 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const NTLMAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const ntlmAuth = get(collection, 'root.request.auth.ntlm', {});
const ntlmAuth = collection.draft?.root ? get(collection, 'draft.root.request.auth.ntlm', {}) : get(collection, 'root.request.auth.ntlm', {});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(ntlmAuth?.password);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
@@ -67,26 +60,24 @@ 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}
@@ -94,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 || ''}
@@ -106,6 +98,7 @@ const NTLMAuth = ({ collection }) => {
onSave={handleSave}
onChange={(val) => handleDomainChange(val)}
collection={collection}
isCompact
/>
</div>
</StyledWrapper>

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
font-size: ${(props) => props.theme.font.size.base};
}
.single-line-editor-wrapper {
max-width: 400px;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import get from 'lodash/get';
import StyledWrapper from './StyledWrapper';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import OAuth2AuthorizationCode from 'components/RequestPane/Auth/OAuth2/AuthorizationCode/index';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index';
import { useDispatch } from 'react-redux';
@@ -10,14 +10,14 @@ import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCr
import OAuth2Implicit from 'components/RequestPane/Auth/OAuth2/Implicit/index';
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
const GrantTypeComponentMap = ({collection }) => {
const GrantTypeComponentMap = ({ collection }) => {
const dispatch = useDispatch();
const save = () => {
dispatch(saveCollectionRoot(collection.uid));
dispatch(saveCollectionSettings(collection.uid));
};
let request = collection.draft ? get(collection, 'draft.request', {}) : get(collection, 'root.request', {});
let request = collection.draft?.root ? get(collection, 'draft.root.request', {}) : get(collection, 'root.request', {});
const grantType = get(request, 'auth.oauth2.grantType', {});
switch (grantType) {
@@ -40,7 +40,7 @@ const GrantTypeComponentMap = ({collection }) => {
};
const OAuth2 = ({ collection }) => {
let request = collection.draft ? get(collection, 'draft.request', {}) : get(collection, 'root.request', {});
let request = collection.draft?.root ? get(collection, 'draft.root.request', {}) : get(collection, 'root.request', {});
return (
<StyledWrapper className="mt-2 w-full">

View File

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

View File

@@ -6,18 +6,18 @@ import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const WsseAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const wsseAuth = get(collection, 'root.request.auth.wsse', {});
const wsseAuth = collection.draft?.root ? get(collection, 'draft.root.request.auth.wsse', {}) : get(collection, 'root.request.auth.wsse', {});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(wsseAuth?.password);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleUserChange = (username) => {
dispatch(
@@ -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

@@ -8,17 +8,17 @@ import BasicAuth from './BasicAuth';
import DigestAuth from './DigestAuth';
import WsseAuth from './WsseAuth';
import ApiKeyAuth from './ApiKeyAuth/';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import OAuth2 from './OAuth2';
import NTLMAuth from './NTLMAuth';
import Button from 'ui/Button';
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
const authMode = collection.draft?.root ? get(collection, 'draft.root.request.auth.mode') : get(collection, 'root.request.auth.mode');
const dispatch = useDispatch();
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const getAuthView = () => {
switch (authMode) {
@@ -36,7 +36,7 @@ const Auth = ({ collection }) => {
}
case 'ntlm': {
return <NTLMAuth collection={collection} />;
}
}
case 'oauth2': {
return <OAuth2 collection={collection} />;
}
@@ -60,9 +60,9 @@ const Auth = ({ collection }) => {
</div>
{getAuthView()}
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
<Button type="submit" size="sm" onClick={handleSave}>
Save
</button>
</Button>
</div>
</StyledWrapper>
);

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;
}
}
@@ -48,32 +48,37 @@ const StyledWrapper = styled.div`
}
.protocol-https,
.protocol-grpcs {
.protocol-grpcs,
.protocol-wss {
position: absolute;
right: 8px;
top: 0;
bottom: 0;
transition: transform 0.3s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
}
.protocol-https {
animation: slideUpDown 6s infinite;
animation: slideUpDown 9s infinite;
transform: translateY(0);
}
.protocol-grpcs {
animation: slideUpDown 6s infinite 3s;
animation: slideUpDown 9s infinite 3s;
transform: translateY(100%);
}
.protocol-wss {
animation: slideUpDown 9s infinite 6s;
transform: translateY(100%);
}
@keyframes slideUpDown {
0%, 45% {
0%, 30% {
transform: translateY(0);
}
50%, 95% {
33.33%, 97% {
transform: translateY(100%);
}
100% {

View File

@@ -1,19 +1,31 @@
import React from 'react';
import { IconCertificate, IconTrash, IconWorld } from '@tabler/icons';
import { useFormik } from 'formik';
import { uuid } from 'utils/common';
import * as Yup from 'yup';
import { IconEye, IconEyeOff } from '@tabler/icons';
import { useState } from 'react';
import StyledWrapper from './StyledWrapper';
import { useRef } from 'react';
import path from 'utils/common/path';
import SensitiveFieldWarning from 'components/SensitiveFieldWarning/index';
import SingleLineEditor from 'components/SingleLineEditor/index';
import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField/index';
import { useTheme } from 'styled-components';
import { useDispatch } from 'react-redux';
import { updateCollectionClientCertificates } from 'providers/ReduxStore/slices/collections';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import get from 'lodash/get';
import Button from 'ui/Button';
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
const ClientCertSettings = ({ collection }) => {
const dispatch = useDispatch();
// Get client certs from draft if exists, otherwise from brunoConfig
const clientCertConfig = collection.draft?.brunoConfig
? get(collection, 'draft.brunoConfig.clientCertificates.certs', [])
: get(collection, 'brunoConfig.clientCertificates.certs', []);
const certFilePathInputRef = useRef();
const keyFilePathInputRef = useRef();
const pfxFilePathInputRef = useRef();
const { storedTheme } = useTheme();
const formik = useFormik({
initialValues: {
@@ -28,7 +40,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
domain: Yup.string()
.required()
.trim()
.test('not-empty-after-trim', 'Domain is required', value => value && value.trim().length > 0),
.test('not-empty-after-trim', 'Domain is required', (value) => value && value.trim().length > 0),
type: Yup.string().required().oneOf(['cert', 'pfx']),
certFilePath: Yup.string().when('type', {
is: (type) => type == 'cert',
@@ -62,28 +74,47 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
passphrase: values.passphrase
};
}
onUpdate(relevantValues);
// Add the new cert to the existing certs in draft
const updatedCerts = [...clientCertConfig, relevantValues];
const clientCertificates = {
enabled: true,
certs: updatedCerts
};
dispatch(updateCollectionClientCertificates({
collectionUid: collection.uid,
clientCertificates
}));
formik.resetForm();
resetFileInputFields();
}
});
const { isSensitive } = useDetectSensitiveField(collection);
const { showWarning, warningMessage } = isSensitive(formik.values.passphrase);
const getFile = (e) => {
const filePath = window?.ipcRenderer?.getFilePath(e?.files?.[0]);
if (filePath) {
let relativePath = path.relative(root, filePath);
let relativePath = path.relative(collection.pathname, filePath);
formik.setFieldValue(e.name, relativePath);
}
};
const resetFileInputFields = () => {
certFilePathInputRef.current.value = '';
keyFilePathInputRef.current.value = '';
pfxFilePathInputRef.current.value = '';
if (certFilePathInputRef.current) {
certFilePathInputRef.current.value = '';
}
if (keyFilePathInputRef.current) {
keyFilePathInputRef.current.value = '';
}
if (pfxFilePathInputRef.current) {
pfxFilePathInputRef.current.value = '';
}
};
const [passwordVisible, setPasswordVisible] = useState(false);
const handleTypeChange = (e) => {
formik.setFieldValue('type', e.target.value);
if (e.target.value === 'cert') {
@@ -97,34 +128,49 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
}
};
const handleRemove = (indexToRemove) => {
const updatedCerts = clientCertConfig.filter((cert, index) => index !== indexToRemove);
const clientCertificates = {
enabled: true,
certs: updatedCerts
};
dispatch(updateCollectionClientCertificates({
collectionUid: collection.uid,
clientCertificates
}));
};
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
return (
<StyledWrapper className="w-full h-full">
<div className="text-xs mb-4 text-muted">Add client certificates to be used for specific domains.</div>
<h1 className="font-semibold">Client Certificates</h1>
<h1 className="font-medium">Client Certificates</h1>
<ul className="mt-4">
{!clientCertConfig.length
? 'No client certificates added'
: clientCertConfig.map((clientCert, index) => (
<li key={`client-cert-${index}`} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex w-full items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
<li key={`client-cert-${index}`} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex w-full items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
</div>
<div className="flex w-full items-center">
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
</div>
<button onClick={() => handleRemove(index)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>
</div>
<div className="flex w-full items-center">
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
</div>
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>
</div>
</li>
))}
</li>
))}
</ul>
<h1 className="font-semibold mt-8 mb-2">Add Client Certificate</h1>
<h1 className="font-medium mt-8 mb-2">Add Client Certificate</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="domain">
@@ -135,6 +181,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
<span className="protocol-placeholder">
<span className="protocol-https">https://</span>
<span className="protocol-grpcs">grpcs://</span>
<span className="protocol-wss">wss://</span>
</span>
</div>
<input
@@ -314,30 +361,27 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
Passphrase
</label>
<div className="textbox flex flex-row items-center w-[300px] h-[1.70rem] relative">
<input
id="passphrase"
type={passwordVisible ? 'text' : 'password'}
name="passphrase"
className="outline-none w-64 bg-transparent"
onChange={formik.handleChange}
<SingleLineEditor
value={formik.values.passphrase || ''}
theme={storedTheme}
onChange={(val) => formik.setFieldValue('passphrase', val)}
collection={collection}
isSecret={true}
/>
<button
type="button"
className="btn btn-sm absolute right-0 l"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={1.5} /> : <IconEye size={18} strokeWidth={1.5} />}
</button>
{showWarning && <SensitiveFieldWarning fieldName="basic-password" warningMessage={warningMessage} />}
</div>
{formik.touched.passphrase && formik.errors.passphrase ? (
<div className="ml-1 text-red-500">{formik.errors.passphrase}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
<div className="mt-6 flex flex-row gap-2 items-center">
<Button type="submit" size="sm" data-testid="add-client-cert">
Add
</button>
</Button>
<div className="h-4 border-l border-gray-600"></div>
<Button type="button" size="sm" onClick={handleSave}>
Save
</Button>
</div>
</form>
</StyledWrapper>

View File

@@ -1,20 +1,22 @@
import 'github-markdown-css/github-markdown.css';
import get from 'lodash/get';
import { updateCollectionDocs } from 'providers/ReduxStore/slices/collections';
import { updateCollectionDocs, deleteCollectionDraft } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
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();
const { displayedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const docs = get(collection, 'root.docs', '');
const docs = collection.draft?.root ? get(collection, 'draft.root.docs', '') : get(collection, 'root.docs', '');
const preferences = useSelector((state) => state.app.preferences);
const toggleViewMode = () => {
@@ -31,41 +33,41 @@ const Docs = ({ collection }) => {
};
const handleDiscardChanges = () => {
dispatch(
dispatch((
updateCollectionDocs({
collectionUid: collection.uid,
docs: docs
})
}))
);
toggleViewMode();
}
};
const onSave = () => {
dispatch(saveCollectionRoot(collection.uid));
dispatch(saveCollectionSettings(collection.uid));
toggleViewMode();
}
};
return (
<StyledWrapper className="h-full w-full relative flex flex-col">
<div className='flex flex-row w-full justify-between items-center mb-4'>
<div className='text-lg font-medium flex items-center gap-2'>
<div className="flex flex-row w-full justify-between items-center mb-4">
<div className="text-lg font-medium flex items-center gap-2">
<IconFileText size={20} strokeWidth={1.5} />
Documentation
</div>
<div className='flex flex-row gap-2 items-center justify-center'>
<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>
@@ -81,14 +83,13 @@ const Docs = ({ collection }) => {
fontSize={get(preferences, 'font.codeFontSize')}
/>
) : (
<div className='h-full overflow-auto pl-1'>
<div className='h-[1px] min-h-[500px]'>
<div className="h-full overflow-auto pl-1">
<div className="h-[1px] min-h-[500px]">
{
docs?.length > 0 ?
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
:
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={documentationPlaceholder} />
}
docs?.length > 0
? <Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
: <Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={documentationPlaceholder} />
}
</div>
</div>
)}
@@ -98,7 +99,6 @@ const Docs = ({ collection }) => {
export default Docs;
const documentationPlaceholder = `
Welcome to your collection documentation! This space is designed to help you document your API collection effectively.

View File

@@ -6,7 +6,7 @@ const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
font-weight: 500;
table-layout: fixed;
thead,
@@ -16,7 +16,7 @@ const Wrapper = styled.div`
thead {
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
font-size: ${(props) => props.theme.font.size.base};
user-select: none;
}
td {
@@ -33,7 +33,7 @@ const Wrapper = styled.div`
}
.btn-add-header {
font-size: 0.8125rem;
font-size: ${(props) => props.theme.font.size.base};
}
input[type='text'] {

View File

@@ -1,77 +1,78 @@
import React, { useState } from 'react';
import React, { useState, useCallback } from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import {
addCollectionHeader,
updateCollectionHeader,
deleteCollectionHeader,
setCollectionHeaders
} from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { setCollectionHeaders } from 'providers/ReduxStore/slices/collections';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import EditableTable from 'components/EditableTable';
import StyledWrapper from './StyledWrapper';
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';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const Headers = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const headers = get(collection, 'root.request.headers', []);
const headers = collection.draft?.root
? get(collection, 'draft.root.request.headers', [])
: get(collection, 'root.request.headers', []);
const [isBulkEditMode, setIsBulkEditMode] = useState(false);
const toggleBulkEditMode = () => {
setIsBulkEditMode(!isBulkEditMode);
};
const handleBulkHeadersChange = (newHeaders) => {
dispatch(setCollectionHeaders({ collectionUid: collection.uid, headers: newHeaders }));
};
const handleHeadersChange = useCallback((updatedHeaders) => {
dispatch(setCollectionHeaders({ collectionUid: collection.uid, headers: updatedHeaders }));
}, [dispatch, collection.uid]);
const addHeader = () => {
dispatch(
addCollectionHeader({
collectionUid: collection.uid
})
);
};
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleHeaderValueChange = (e, _header, type) => {
const header = cloneDeep(_header);
switch (type) {
case 'name': {
header.name = e.target.value;
break;
}
case 'value': {
header.value = e.target.value;
break;
}
case 'enabled': {
header.enabled = e.target.checked;
break;
}
const columns = [
{
key: 'name',
name: 'Name',
isKeyField: true,
placeholder: 'Name',
width: '30%',
render: ({ row, value, onChange, isLastEmptyRow }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) => onChange(newValue.replace(/[\r\n]/g, ''))}
autocomplete={headerAutoCompleteList}
collection={collection}
placeholder={isLastEmptyRow ? 'Name' : ''}
/>
)
},
{
key: 'value',
name: 'Value',
placeholder: 'Value',
render: ({ row, value, onChange, isLastEmptyRow }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
onSave={handleSave}
onChange={onChange}
collection={collection}
autocomplete={MimeTypes}
placeholder={isLastEmptyRow ? 'Value' : ''}
/>
)
}
dispatch(
updateCollectionHeader({
header: header,
collectionUid: collection.uid
})
);
};
];
const handleRemoveHeader = (header) => {
dispatch(
deleteCollectionHeader({
headerUid: header.uid,
collectionUid: collection.uid
})
);
const defaultRow = {
name: '',
value: '',
description: ''
};
if (isBulkEditMode) {
@@ -82,7 +83,7 @@ const Headers = ({ collection }) => {
</div>
<BulkEditor
params={headers}
onChange={handleBulkHeadersChange}
onChange={handleHeadersChange}
onToggle={toggleBulkEditMode}
onSave={handleSave}
/>
@@ -95,94 +96,24 @@ const Headers = ({ collection }) => {
<div className="text-xs mb-4 text-muted">
Add request headers that will be sent with every request in this collection.
</div>
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{headers && headers.length
? headers.map((header) => {
return (
<tr key={header.uid}>
<td>
<SingleLineEditor
value={header.name}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'name'
)
}
autocomplete={headerAutoCompleteList}
collection={collection}
/>
</td>
<td>
<SingleLineEditor
value={header.value}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'value'
)
}
collection={collection}
autocomplete={MimeTypes}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={header.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<div className="flex justify-between mt-2">
<button className="btn-add-header text-link pr-2 py-3 select-none" onClick={addHeader}>
+ Add Header
</button>
<EditableTable
columns={columns}
rows={headers}
onChange={handleHeadersChange}
defaultRow={defaultRow}
/>
<div className="flex justify-end mt-2">
<button className="text-link select-none" onClick={toggleBulkEditMode}>
Bulk Edit
</button>
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
<Button type="submit" size="sm" onClick={handleSave}>
Save
</button>
</Button>
</div>
</StyledWrapper>
);
};
export default Headers;

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,33 +1,44 @@
import React from "react";
import React from 'react';
import { getTotalRequestCountInCollection } from 'utils/collections/';
import { IconFolder, IconWorld, IconApi, IconShare } from '@tabler/icons';
import { areItemsLoading, getItemsLoadStats } from "utils/collections/index";
import { useState } from "react";
import ShareCollection from "components/ShareCollection/index";
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();
const totalRequestsInCollection = getTotalRequestCountInCollection(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);
const collectionEnvironmentCount = collection.environments?.length || 0;
const globalEnvironmentCount = globalEnvironments?.length || 0;
const handleToggleShowShareCollectionModal = (value) => (e) => {
toggleShowShareCollectionModal(value);
}
};
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-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-semibold text-sm">Location</div>
<div className="mt-1 text-sm text-muted break-all">
<div className="font-medium">Location</div>
<div className="mt-1 text-muted break-all">
{collection.pathname}
</div>
</div>
@@ -35,48 +46,90 @@ 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-semibold text-sm">Environments</div>
<div className="mt-1 text-sm text-muted">
{collection.environments?.length || 0} environment{collection.environments?.length !== 1 ? 's' : ''} configured
<div className="font-medium">Environments</div>
<div className="mt-1 flex flex-col gap-1">
<button
type="button"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
uid: `${collection.uid}-environment-settings`,
collectionUid: collection.uid,
type: 'environment-settings'
})
);
}}
>
{collectionEnvironmentCount} collection environment{collectionEnvironmentCount !== 1 ? 's' : ''}
</button>
<button
type="button"
className="text-link cursor-pointer hover:underline text-left bg-transparent"
onClick={() => {
dispatch(
addTab({
uid: `${collection.uid}-global-environment-settings`,
collectionUid: collection.uid,
type: 'global-environment-settings'
})
);
}}
>
{globalEnvironmentCount} global environment{globalEnvironmentCount !== 1 ? 's' : ''}
</button>
</div>
</div>
</div>
{/* 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-semibold text-sm">Requests</div>
<div className="mt-1 text-sm text-muted">
<div className="font-medium">Requests</div>
<div className="mt-1 text-muted">
{
isCollectionLoading? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
isCollectionLoading ? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
}
</div>
</div>
</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-semibold text-sm h-fit my-auto">Share</div>
<div className="mt-1 text-sm group-hover:underline text-link">
<div className="font-medium h-fit my-auto">Share</div>
<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>
);
};
export default Info;
export default Info;

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

@@ -1,27 +1,25 @@
import React from 'react';
import { flattenItems } from "utils/collections";
import { flattenItems } from 'utils/collections';
import { IconAlertTriangle } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper";
import StyledWrapper from './StyledWrapper';
import { useDispatch, useSelector } from 'react-redux';
import { isItemARequest, itemIsOpenedInTabs } from 'utils/tabs/index';
import { getDefaultRequestPaneTab } from 'utils/collections/index';
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
import { hideHomePage } from 'providers/ReduxStore/slices/app';
const RequestsNotLoaded = ({ collection }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
const flattenedItems = flattenItems(collection.items);
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
const itemsFailedLoading = flattenedItems?.filter((item) => item?.partial && !item?.loading);
if (!itemsFailedLoading?.length) {
return null;
}
const handleRequestClick = (item) => e => {
const handleRequestClick = (item) => (e) => {
e.preventDefault();
if (isItemARequest(item)) {
dispatch(hideHomePage());
if (itemIsOpenedInTabs(item, tabs)) {
dispatch(
focusTab({
@@ -39,12 +37,12 @@ const RequestsNotLoaded = ({ collection }) => {
);
return;
}
}
};
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">
@@ -61,7 +59,7 @@ const RequestsNotLoaded = ({ collection }) => {
<tbody>
{flattenedItems?.map((item, index) => (
item?.partial && !item?.loading ? (
<tr key={index} className='cursor-pointer' onClick={handleRequestClick(item)}>
<tr key={index} className="cursor-pointer" onClick={handleRequestClick(item)}>
<td className="py-1.5 px-3">
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
</td>

View File

@@ -1,16 +1,16 @@
import StyledWrapper from "./StyledWrapper";
import Docs from "../Docs";
import Info from "./Info";
import StyledWrapper from './StyledWrapper';
import Docs from '../Docs';
import Info from './Info';
import { IconBox } from '@tabler/icons';
import RequestsNotLoaded from "./RequestsNotLoaded";
import RequestsNotLoaded from './RequestsNotLoaded';
const Overview = ({ collection }) => {
return (
<div className="h-full">
<div className="grid grid-cols-5 gap-4 h-full">
<div className="grid grid-cols-5 gap-5 h-full">
<div className="col-span-2">
<div className="text-xl font-semibold flex items-center gap-2">
<IconBox size={24} stroke={1.5} />
<div className="text-lg font-medium flex items-center gap-2">
<IconBox size={20} stroke={1.5} />
{collection?.name}
</div>
<Info collection={collection} />
@@ -22,6 +22,6 @@ const Overview = ({ collection }) => {
</div>
</div>
);
}
};
export default Overview;
export default Overview;

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

@@ -1,39 +1,47 @@
import React from 'react';
import { useFormik } from 'formik';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
import cloneDeep from 'lodash/cloneDeep';
import { updateCollectionPresets } from 'providers/ReduxStore/slices/collections';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import { get } from 'lodash';
import Button from 'ui/Button';
const PresetsSettings = ({ collection }) => {
const dispatch = useDispatch();
const {
brunoConfig: { presets: presets = {} }
} = collection;
const initialPresets = { requestType: 'http', requestUrl: '' };
const formik = useFormik({
enableReinitialize: true,
initialValues: {
requestType: presets.requestType || 'http',
requestUrl: presets.requestUrl || ''
},
onSubmit: (newPresets) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.presets = newPresets;
dispatch(updateBrunoConfig(brunoConfig, collection.uid));
toast.success('Collection presets updated');
}
});
// Get presets from draft.brunoConfig if it exists, otherwise from brunoConfig
const currentPresets = collection.draft?.brunoConfig
? get(collection, 'draft.brunoConfig.presets', initialPresets)
: get(collection, 'brunoConfig.presets', initialPresets);
// Helper to update presets config
const updatePresets = (updates) => {
const updatedPresets = { ...currentPresets, ...updates };
dispatch(updateCollectionPresets({
collectionUid: collection.uid,
presets: updatedPresets
}));
};
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleRequestTypeChange = (e) => {
updatePresets({ requestType: e.target.value });
};
const handleRequestUrlChange = (e) => {
updatePresets({ requestUrl: e.target.value });
};
return (
<StyledWrapper className="h-full w-full">
<div className="text-xs mb-4 text-muted">
These presets will be used as the default values for new requests in this collection.
</div>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="bruno-form">
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
<label className="settings-label flex items-center" htmlFor="http">
Request Type
</label>
<div className="flex items-center">
@@ -42,9 +50,9 @@ const PresetsSettings = ({ collection }) => {
className="cursor-pointer"
type="radio"
name="requestType"
onChange={formik.handleChange}
onChange={handleRequestTypeChange}
value="http"
checked={formik.values.requestType === 'http'}
checked={(currentPresets.requestType || 'http') === 'http'}
/>
<label htmlFor="http" className="ml-1 cursor-pointer select-none">
HTTP
@@ -55,9 +63,9 @@ const PresetsSettings = ({ collection }) => {
className="ml-4 cursor-pointer"
type="radio"
name="requestType"
onChange={formik.handleChange}
onChange={handleRequestTypeChange}
value="graphql"
checked={formik.values.requestType === 'graphql'}
checked={(currentPresets.requestType || 'http') === 'graphql'}
/>
<label htmlFor="graphql" className="ml-1 cursor-pointer select-none">
GraphQL
@@ -68,17 +76,30 @@ const PresetsSettings = ({ collection }) => {
className="ml-4 cursor-pointer"
type="radio"
name="requestType"
onChange={formik.handleChange}
onChange={handleRequestTypeChange}
value="grpc"
checked={formik.values.requestType === 'grpc'}
checked={(currentPresets.requestType || 'http') === 'grpc'}
/>
<label htmlFor="grpc" className="ml-1 cursor-pointer select-none">
gRPC
</label>
<input
id="ws"
className="ml-4 cursor-pointer"
type="radio"
name="requestType"
onChange={handleRequestTypeChange}
value="ws"
checked={(currentPresets.requestType || 'http') === 'ws'}
/>
<label htmlFor="ws" className="ml-1 cursor-pointer select-none">
WebSocket
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="requestUrl">
<label className="settings-label" htmlFor="request-url">
Base URL
</label>
<div className="flex items-center w-full">
@@ -93,8 +114,8 @@ const PresetsSettings = ({ collection }) => {
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
onChange={handleRequestUrlChange}
value={currentPresets.requestUrl || ''}
style={{ width: '100%' }}
/>
</div>
@@ -102,11 +123,11 @@ const PresetsSettings = ({ collection }) => {
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
<Button type="button" size="sm" onClick={handleSave}>
Save
</button>
</Button>
</div>
</form>
</div>
</StyledWrapper>
);
};

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

@@ -1,4 +1,5 @@
import React, { useRef } from 'react';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import {
IconTrash,
@@ -10,8 +11,11 @@ import {
import { getBasename } from 'utils/common/path';
import { Tooltip } from 'react-tooltip';
import useProtoFileManagement from '../../../hooks/useProtoFileManagement';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import Button from 'ui/Button';
const ProtobufSettings = ({ collection }) => {
const dispatch = useDispatch();
const {
protoFiles,
importPaths,
@@ -27,6 +31,8 @@ const ProtobufSettings = ({ collection }) => {
} = useProtoFileManagement(collection);
const fileInputRef = useRef(null);
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
// Get file path using the ipcRenderer
const getProtoFile = async (event) => {
const files = event?.files;
@@ -107,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-semibold text-sm 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"
@@ -125,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>
@@ -134,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>
@@ -148,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-sm 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>
@@ -161,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="text-sm font-medium text-gray-900 dark:text-gray-100">
<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} />
@@ -190,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"
>
@@ -214,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-semibold text-sm 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"
@@ -232,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>
@@ -241,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>
@@ -257,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-sm 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>
@@ -270,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="text-sm 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} />
@@ -309,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"
>
@@ -329,6 +335,12 @@ const ProtobufSettings = ({ collection }) => {
</div>
</div>
<div className="mt-6">
<Button type="button" size="sm" onClick={handleSave}>
Save
</Button>
</div>
</StyledWrapper>
);
};

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

@@ -1,115 +1,214 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import React from 'react';
import InfoTip from 'components/InfoTip';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
import { IconEye, IconEyeOff } from '@tabler/icons';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { updateCollectionProxy } from 'providers/ReduxStore/slices/collections';
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
import { get } from 'lodash';
import toast from 'react-hot-toast';
import Button from 'ui/Button';
const ProxySettings = ({ proxyConfig, onUpdate }) => {
const proxySchema = Yup.object({
enabled: Yup.string().oneOf(['global', 'true', 'false']),
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string()
.when('enabled', {
is: 'true',
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable()
})
.max(1024),
port: Yup.number()
.min(1)
.max(65535)
.typeError('Specify port between 1 and 65535')
.nullable()
.transform((_, val) => (val ? Number(val) : null)),
auth: Yup.object()
.when('enabled', {
is: 'true',
then: Yup.object({
enabled: Yup.boolean(),
username: Yup.string()
.when('enabled', {
is: true,
then: (username) => username.required('Specify username for proxy authentication.')
})
.max(1024),
password: Yup.string()
.when('enabled', {
is: true,
then: (password) => password.required('Specify password for proxy authentication.')
})
.max(1024)
})
})
.optional(),
bypassProxy: Yup.string().optional().max(1024)
});
const formik = useFormik({
initialValues: {
enabled: proxyConfig.enabled || 'global',
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
const ProxySettings = ({ collection }) => {
const dispatch = useDispatch();
const initialProxyConfig = {
inherit: true,
config: {
protocol: 'http',
hostname: '',
port: '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
username: '',
password: ''
},
bypassProxy: proxyConfig.bypassProxy || ''
},
validationSchema: proxySchema,
onSubmit: (values) => {
proxySchema
.validate(values, { abortEarly: true })
.then((validatedProxy) => {
// serialize 'enabled' to boolean
if (validatedProxy.enabled === 'true') {
validatedProxy.enabled = true;
} else if (validatedProxy.enabled === 'false') {
validatedProxy.enabled = false;
}
onUpdate(validatedProxy);
})
.catch((error) => {
let errMsg = error.message || 'Preferences validation error';
toast.error(errMsg);
});
bypassProxy: ''
}
});
};
// Get proxy from draft.brunoConfig if it exists, otherwise from brunoConfig
const currentProxyConfig = collection.draft?.brunoConfig
? get(collection, 'draft.brunoConfig.proxy', initialProxyConfig)
: get(collection, 'brunoConfig.proxy', initialProxyConfig);
const [passwordVisible, setPasswordVisible] = useState(false);
useEffect(() => {
formik.setValues({
enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global',
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
},
bypassProxy: proxyConfig.bypassProxy || ''
const validateHostnameOnChange = (hostname) => {
if (hostname && hostname.length > 1024) {
toast.error('Hostname must be less than 1024 characters');
return false;
}
return true;
};
const validatePortOnChange = (port) => {
if (!port || port === '') {
return true; // Allow empty port during typing
}
const portNum = Number(port);
if (isNaN(portNum)) {
toast.error('Port must be a valid number');
return false;
}
if (portNum < 1 || portNum > 65535) {
toast.error('Port must be between 1 and 65535');
return false;
}
return true;
};
const validateAuthUsernameOnChange = (username) => {
if (username && username.length > 1024) {
toast.error('Username must be less than 1024 characters');
return false;
}
return true;
};
const validateAuthPasswordOnChange = (password) => {
if (password && password.length > 1024) {
toast.error('Password must be less than 1024 characters');
return false;
}
return true;
};
const validateBypassProxyOnChange = (bypassProxy) => {
if (bypassProxy && bypassProxy.length > 1024) {
toast.error('Bypass proxy must be less than 1024 characters');
return false;
}
return true;
};
// Helper to update proxy config
const updateProxy = (updates) => {
const updatedProxy = { ...currentProxyConfig, ...updates };
dispatch(updateCollectionProxy({
collectionUid: collection.uid,
proxy: updatedProxy
}));
};
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
const handleEnabledChange = (e) => {
const value = e.target.value;
// 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({
config: {
...currentProxyConfig.config,
protocol: e.target.value
}
});
}, [proxyConfig]);
};
const handleHostnameChange = (e) => {
const hostname = e.target.value;
if (validateHostnameOnChange(hostname)) {
updateProxy({
config: {
...currentProxyConfig.config,
hostname
}
});
}
};
const handlePortChange = (e) => {
const port = e.target.value ? Number(e.target.value) : '';
if (validatePortOnChange(port)) {
updateProxy({
config: {
...currentProxyConfig.config,
port
}
});
}
};
const handleAuthEnabledChange = (e) => {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
disabled: !e.target.checked
}
}
});
};
const handleAuthUsernameChange = (e) => {
const username = e.target.value;
if (validateAuthUsernameOnChange(username)) {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
username
}
}
});
}
};
const handleAuthPasswordChange = (e) => {
const password = e.target.value;
if (validateAuthPasswordOnChange(password)) {
updateProxy({
config: {
...currentProxyConfig.config,
auth: {
...currentProxyConfig.config.auth,
password
}
}
});
}
};
const handleBypassProxyChange = (e) => {
const bypassProxy = e.target.value;
if (validateBypassProxyOnChange(bypassProxy)) {
updateProxy({
config: {
...currentProxyConfig.config,
bypassProxy
}
});
}
};
// 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">
<div className="text-xs mb-4 text-muted">Configure proxy settings for this collection.</div>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="bruno-form">
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
Config
<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>
@@ -119,20 +218,20 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
<input
type="radio"
name="enabled"
value="global"
checked={formik.values.enabled === 'global'}
onChange={formik.handleChange}
value="inherit"
checked={enabledValue === 'inherit'}
onChange={handleEnabledChange}
className="mr-1"
/>
global
inherit
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="enabled"
value={'true'}
checked={formik.values.enabled === 'true'}
onChange={formik.handleChange}
value="true"
checked={enabledValue === 'true'}
onChange={handleEnabledChange}
className="mr-1"
/>
enabled
@@ -141,196 +240,185 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
<input
type="radio"
name="enabled"
value={'false'}
checked={formik.values.enabled === 'false'}
onChange={formik.handleChange}
value="false"
checked={enabledValue === 'false'}
onChange={handleEnabledChange}
className="mr-1"
/>
disabled
</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={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
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={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</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={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</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={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</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={formik.values.auth.password}
onChange={formik.handleChange}
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>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</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={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</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" className="submit btn btn-sm btn-secondary">
<Button type="submit" size="sm" onClick={handleSave}>
Save
</button>
</Button>
</div>
</form>
</div>
</StyledWrapper>
);
};
export default ProxySettings;
export default ProxySettings;

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