Compare commits

...

304 Commits

Author SHA1 Message Date
ramki-bruno
07682ba7b5 Fix: CLI-Tests Workflow lacks contents:read permission 2025-02-11 15:23:12 +05:30
lohxt1
8810b9e291 fix graphql variables editor and tests editor height 2025-02-10 21:42:53 +05:30
Sanjai Kumar
89a4cd62bc Feat: Move-Collection with Drag-and-Drop (#3755)
---------

Co-authored-by: sanjai0py <sanjailucifer666@gmail.com>
Co-authored-by: ramki-bruno <ramki@usebruno.com>
2025-02-10 20:46:42 +05:30
Anoop M D
4c1765e9f9 feat(#2896): add support for cheerio and xml2json as inbuilt library 2025-02-10 18:55:20 +05:30
Sreelakshmi Jayarajan
8351589994 Fixing typo in pm translation of util function 2025-02-10 18:55:11 +05:30
Sreelakshmi Jayarajan
4d063b2d0b Revert "Fixing typo in pm translation of util functions"
This reverts commit fceff56872.
2025-02-10 18:55:11 +05:30
Sreelakshmi Jayarajan
288628003f Fixing typo in pm translation of util functions 2025-02-10 18:55:11 +05:30
Pooja Belaramani
ced6ddfab5 add: translation for skipRequest and stopExecution 2025-02-10 11:45:23 +05:30
Pragadesh-45
5291bbaef7 Fix: Import failing for collections with special characters in Windows (#3969)
* fix: correct variable used in collection name update
* fix: sanitize collection names by removing invalid filesystem characters
* refactor: refactor collection name sanitization to use `sanitizeDirectoryName`
2025-02-10 11:37:50 +05:30
ramki-bruno
667b15386c Fix: Unresponsive click-area in folder-collection-item between chevron-icon and name 2025-02-06 23:34:58 +05:30
ramki-bruno
086e943c04 Fix: Collectio-items not loading when clicking on chevron icon the first-time 2025-02-06 23:34:58 +05:30
ramki-bruno
901c4e1ca2 Fix: Request-send action should make the Tab permanent/non-preview 2025-02-06 23:34:58 +05:30
sanjai0py
14e9227e07 refactor: enhance JSON parsing logic for Buffer responses 2025-02-06 21:24:55 +05:30
sanjai0py
d8a2e6f405 fix: improve handling of Buffer responses and set default charset to utf-8 2025-02-06 21:24:55 +05:30
lohxt1
5c096cff7b fix worker lane logic 2025-02-06 21:20:23 +05:30
Sanjai Kumar
0913e668e0 refactor: update browseFiles action to remove default properties and improve file dialog handling (#3957)
---------
Co-authored-by: sanjai0py <sanjailucifer666@gmail.com>
2025-02-06 20:26:49 +05:30
Sahil Khan
f0c3125227 fix:handle render svg response (#3833) 2025-02-06 20:23:27 +05:30
naman-bruno
a5d23599a1 Fixed: user cookie header getting overwritten by stored cookie in cli 2025-02-06 20:03:42 +05:30
naman-bruno
28ca0494e0 Fixed: user cookie header getting overwritten by stored cookie 2025-02-06 20:03:42 +05:30
naman-bruno
722d9788ca Feature: Improve tab UX (#3831)
---------
Co-authored-by: ramki-bruno <ramki@usebruno.com>
2025-02-06 19:34:10 +05:30
lohit
038f2d1f0b temp. revert tests for file body (#3941)
* temporarily revert tests for file body
2025-02-04 22:12:59 +05:30
lohit
8f604efc7e fixes tests for the file body pr (#3940)
fixes tests for bruno-app and bruno-electron
2025-02-04 22:12:59 +05:30
Sanjai Kumar
af182a9c00 refactor: rename binaryFile to file and update related references 2025-02-04 22:12:59 +05:30
Sanjai Kumar
dd23962958 improvements (#3930)
Co-authored-by: Sanjai Kumar <sanjai@usebruno.com>
2025-02-04 22:12:59 +05:30
Marcos Adriano
324b7cec51 feat: raw binary files handling in request body (#3734)
--------------
Co-authored-by: lohit <lohit@usebruno.com>
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-02-04 22:12:59 +05:30
lohxt1
8abf8ff9c8 skipped request should not be considered as errors in junit reports 2025-02-04 21:48:10 +05:30
Pragadesh-45
ec5a5c9b56 fix: correct variable used in collection name update 2025-02-04 18:49:02 +05:30
Pooja Belaramani
4598acd068 fix 2025-02-03 13:49:35 +05:30
Pooja Belaramani
062ab00a66 fix: ensure API key values are converted to strings in Postman collection importer. 2025-02-03 13:49:35 +05:30
Pooja Belaramani
650cb47a8b fix: resolve function recusion 2025-02-03 13:01:24 +05:30
Pooja Belaramani
08139a8f3e fix 2025-01-30 09:58:38 +05:30
Pooja Belaramani
51e087efba add: hint word for runRequest, sendNextRequest, skipRequest, getTestResults 2025-01-30 09:58:38 +05:30
lohxt1
ff5683f19f add runRequest and runner utils functions to cli
~ add bru.runRequest support for cli
~ add bru.runner.skipRequest, bru.runner.stopExecution support for cli
2025-01-29 11:53:02 +05:30
lohxt1
d13e4b3b54 ensure variables set in scripts/tests during bru.runRequest reflect in original request scripts/tests 2025-01-29 11:53:02 +05:30
Jarod Gowgiel
2d08567d8d Add an npm script to run the Electron app for debugging (#3616)
This allows for developers to attach Dev Tools, e.g. the Chrome
"dedicated DevTools for node", to the main Electron process
for debugging operations that occur on the main process.
2025-01-29 11:25:41 +05:30
ramki-bruno
b51f8109a2 Review changes and minor-refactoring in Reveal in Finder feature 2025-01-29 11:18:57 +05:30
Naman
8f241a32ae Feature: Reveal in Finder (#3698)
* feat: Reveal in Finder

* added support for linux
2025-01-29 11:18:57 +05:30
Sanjai Kumar
b7fda331dc Fix: Comment toggling for JSON modes in CodeEditor 2025-01-29 11:16:15 +05:30
lohxt1
10e0fde2a8 use lowercase header keys while making requests 2025-01-29 08:34:01 +05:30
Anoop M D
9f5f975f70 feat: async parser workers (#3834) (#3887)
feat: async parser workers (#3834)
Co-authored-by: lohit <lohit.jiddimani@gmail.com>
2025-01-29 02:53:53 +05:30
Pragadesh-45
b5bd259a1b fix: handle Windows paths in cloneItem and getDirectoryName functions (fixes: #3401) (#3646)
* fix: handle Windows paths in cloneItem and getDirectoryName functions

* chore: removed commented lines

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-01-28 21:32:51 +05:30
lohit
0633d45a10 upgraded axios libarary (#3899) 2025-01-28 18:55:59 +05:30
Pragadesh-45
16e27d2ca4 feat: make BrunoResponse callable to access body data using expressions fixes (#481) (#3710)
* feat: make `BrunoResponse` callable to access body data using expressions
2025-01-28 14:57:00 +05:30
Alex
956d5a38e9 fix: bump axios version to handle ivp6 (#3878)
Co-authored-by: alex.chua <alex.chua@bytedance.com>
2025-01-28 14:36:29 +05:30
Sanjai Kumar
bfc1101371 refactor: update GitHub Actions workflow to add permissions for checks and pull requests for the cli-tests job (#3844) 2025-01-28 14:13:14 +05:30
sanish chirayath
05be59f00c style: update StyledWrapper to use flex layout and adjust dialog positioning (#3888) 2025-01-27 18:49:05 +05:30
tlaloc911
fee631d496 passing defaults instead of axiosInstance to NTLMClient (#3841) 2025-01-18 21:34:19 +05:30
Bobby Bonestell
d03de2b622 fix: Inherited apikey auth mapping for bruno-cli (#3512)
* Added bruno-cli support for mapping inherited apikey auth from collection

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-01-17 20:33:53 +05:30
sanish chirayath
31b2818821 fix: modal - provide default handleConfirm function and update dependencies in useEffect (#3830) 2025-01-17 14:52:06 +05:30
naman-bruno
8a71dfc022 enhancement: moved collection click area from name to div (#3813) 2025-01-17 13:20:53 +05:30
naman-bruno
3e6204e49b Fix: Horizontal Rules missing in markdown docs preview (#3814) 2025-01-17 13:10:16 +05:30
naman-bruno
dab4bb6a1c fix: hide env dropdown on configure (#3826) 2025-01-17 13:07:39 +05:30
Anoop M D
3c8cb702f5 feat: added icons to env modal buttons 2025-01-17 02:46:06 +05:30
Hadi
2df7fd6588 Added activate button to environment window. (#1531)
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-01-17 02:12:33 +05:30
Sanjai Kumar
e5d7cd1be9 feat: add rspack dynamic import configuration to rsbuild (#3819) 2025-01-16 20:11:49 +05:30
pooja-bruno
2bce9b3716 add: document save button for folder and collection settings (#3742) 2025-01-16 20:09:51 +05:30
naman-bruno
5bfcc9b6e7 Fix: Path table is removed when we rearrange items (#3804) 2025-01-16 20:08:30 +05:30
naman-bruno
472b5452f7 Allow rearrangement of table items in params, body, vars, headers, etc… (#3801)
* Allow rearrangement of table items in params, body, vars, headers, and assert

* updated drag function name
2025-01-16 20:06:53 +05:30
pooja-bruno
5b04e0c189 fix: renaming first collection env (#3735) 2025-01-16 20:04:34 +05:30
pooja-bruno
3da12a05db fix: body formurl value disappearing (#3803) 2025-01-16 20:01:48 +05:30
naman-bruno
a73d2a02cf fix: Request vars displayed in red color in body even if they are valid (#3812)
* fix: Request vars displayed in red color in body
* chore: removing index import
---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-01-16 19:59:14 +05:30
lohit
63d3cb380d Merge pull request #3806 from Pragadesh-45/fix/handle-assert-results
Chore/ Remove Unused `RemoveQuotes` function
2025-01-15 20:47:31 +05:30
Pragadesh-45
10a5935a12 Merge branch 'usebruno:main' into fix/handle-assert-results 2025-01-15 20:43:10 +05:30
Pragadesh-45
cf2cb0736e fix: remove commented-out removeQuotes() function 2025-01-15 20:42:02 +05:30
lohit
6abd063749 Merge pull request #3805 from Pragadesh-45/fix/handle-assert-results
Refactor: Improve expression handling across different runtimes (Fix: #3758)
2025-01-15 20:36:07 +05:30
Pragadesh-45
dbf1cad124 fix: remove removeQuotes() 2025-01-15 19:57:06 +05:30
lohit
00c5298b7d Merge pull request #3798 from Pragadesh-45/main
chore: version bump
2025-01-15 15:12:03 +05:30
Pragadesh-45
27ef28ae9b chore: version bump 2025-01-15 13:15:21 +05:30
Pragadesh-45
abb0a7b0db chore: version bump 2025-01-15 13:08:44 +05:30
Anoop M D
d2d7638a54 chore: updated package-lock 2025-01-13 18:49:45 +05:30
tlaloc911
5500070b49 ntlm auth (#3329)
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-01-13 18:48:26 +05:30
Tim Nikischin
72b8c547b2 Order Variables alphabetically (#2982)
Order variables in Variables Tab alphabetically by name
2025-01-13 18:36:08 +05:30
lohit
15b870996d fix: cli - missing iconv-lite import, removed other unused imports (#3767) 2025-01-09 12:09:43 +05:30
lohit
3cb15fc001 fix: cli -- collection run -- clone request item at start (#3760) 2025-01-08 21:26:01 +05:30
Pragadesh-45
96d6bf1664 fix: remove redundant error logging in JSON parsing (#3759) 2025-01-08 21:24:15 +05:30
lohit
f5ff40abfa fix: file input element - missing path property (#3757) 2025-01-08 17:01:57 +05:30
ramki-bruno
c5de2343e9 Added Redux DevTools and React Developer Tools in dev build (#3750)
* Checkin `npm install --package-lock-only` changes

* Added `Redux DevTools` and `React Developer Tools` in dev build
2025-01-08 15:33:13 +05:30
lohit
39e8b66135 Merge pull request #3728 from ganesh-bruno/patch-1
Update package.json with keywords
2025-01-08 12:23:25 +05:30
lohit
9f9294d161 fix: indentation for package json 2025-01-08 12:22:23 +05:30
pooja-bruno
5f63cc4ab4 fix: collection/folder docs when importing postman collection (#3745)
* fix: collection/folder docs when importing postman collection

* fix
2025-01-07 14:27:13 +05:30
lohit
539d22125c Merge pull request #3725 from lohxt1/fix/collection-run-reset-item
fix: collection run - clone request at the start
2025-01-06 16:52:33 +05:30
lohit
36343b30b3 Merge pull request #3738 from Pragadesh-45/refactor/generate-code-item
fix: enhance keyboard navigation for language selection in `GenerateCodeItem`
2025-01-06 16:50:38 +05:30
Pragadesh-45
b5ae2b2b45 fix: enhance keyboard navigation for language selection in GenerateCodeItem 2025-01-06 16:33:17 +05:30
Pragadesh-45
704977f20f Merge remote-tracking branch 'origin/main' into refactor/generate-code-item 2025-01-06 11:40:38 +05:30
ganesh
c852257bda Update package.json with keywords 2025-01-03 07:17:36 -08:00
lohxt1
55eac64ca5 fix: collection run - clone request at the start 2025-01-03 17:31:28 +05:30
lohit
52672e67a2 Merge pull request #3697 from Pragadesh-45/feat/digest-auth-updates
Feat/digest auth updates
2025-01-02 18:24:54 +05:30
Pragadesh-45
d32f987bc6 Merge branch 'usebruno:main' into feat/digest-auth-updates 2025-01-02 18:06:36 +05:30
lohit
5fe9208089 Merge pull request #3641 from lohxt1/fix/cli-system-env-vars
feat: cli -- system level proxy fix
2025-01-02 18:05:39 +05:30
lohit
14ecc09cde Merge pull request #3719 from lohxt1/feat/bru-runner-fns
feat: bru util fns -- skipRequest, stopExecution, getTestResults, getAssertionResults, runRequest
2025-01-02 18:02:42 +05:30
lohit
949bf539b8 Merge pull request #3720 from pooja-bruno/fix/global-env-names-overflow
fix: global env names overflow
2025-01-02 18:01:58 +05:30
lohxt1
c4be6a88e4 fix: removed commented code 2025-01-02 17:56:36 +05:30
lohxt1
99302e3a1d fix: pr review updates 2025-01-02 17:54:29 +05:30
Pragadesh-45
1d3cbd2335 Merge branch 'usebruno:main' into feat/digest-auth-updates 2025-01-02 17:37:33 +05:30
Pooja Belaramani
218d6527df fix: global env names overflow 2025-01-02 16:45:03 +05:30
lohit
42ada4a364 Merge pull request #3708 from sanjaikumar-bruno/handle-invalid-auth-in-pm-export
fix: handle unsupported auth mode by returning 'noauth' type
2025-01-02 12:48:57 +05:30
Pragadesh-45
548f958a0f Refactor/ Implement Focusable Buttons in the Generate Code Modal (#3310)
* refactor: GenerateCodeItem component to fix width issue

* feat: tab switch for languages

* style: add logic to handle different screen sizes

* feat: enhance keyboard navigation for language selection in GenerateCodeItem
2025-01-02 11:17:56 +05:30
lohxt1
005eb273bf chore: fix tests 2025-01-01 20:03:01 +05:30
lohxt1
343e6dae47 chore: updated package lock 2025-01-01 19:56:32 +05:30
lohxt1
7b86febc87 feat: summarize test and assertion results for getTestResults and getAssertionResults fns 2025-01-01 18:06:13 +05:30
lohxt1
ca5fbea7b6 chore: removed console log 2025-01-01 17:52:01 +05:30
lohxt1
f34711c6e0 feat: bru.runRequest in collection run context 2025-01-01 17:50:57 +05:30
lohxt1
cd722a2bd9 feat: bru.runner.setNextRequest 2025-01-01 17:12:57 +05:30
lohxt1
75a9959d47 feat: add quick-js shims for bru runner fns 2025-01-01 15:37:42 +05:30
lohxt1
754a15dd58 feat: add runner functions hintwords for codemirror editor 2025-01-01 15:09:47 +05:30
lohxt1
2a6f6704c3 feat: update skipRequest and stopExecution logic 2025-01-01 15:01:26 +05:30
lohit
7d67239b11 Merge pull request #3664 from pooja-bruno/fix/environment-names-wrapping
fix: env names wrap
2024-12-31 18:10:10 +05:30
lohit
e0ab274452 Merge pull request #3682 from pooja-bruno/fix/asset-cursor-issue-while-editing
fix: asset value cursor issue while editing
2024-12-31 16:39:09 +05:30
lohit
776afbd28a Merge pull request #3674 from pooja-bruno/fix/assertion-clear-value
fix: assertion clear value
2024-12-31 16:38:35 +05:30
lohit
ca8f96fba0 Merge pull request #3712 from pooja-bruno/feature/open-setting-for-folder-and-collection
feat: open setting for folder and collection when
2024-12-31 16:38:13 +05:30
Pooja Belaramani
5c1ab647fc fix: collapse collection on clicking name 2024-12-31 13:01:41 +05:30
Pooja Belaramani
83e63e749e feat: open setting for folder and collection when 2024-12-31 12:25:03 +05:30
lohit
9d94ad9b73 Merge pull request #3711 from lohxt1/fix/aikido--library-version-upgrades
fix: aikido -- library version upgrades
2024-12-30 16:40:24 +05:30
lohit
395fb188fe Merge pull request #53 from lohxt1/fix/aikido-vul
chore: updates
2024-12-30 16:39:56 +05:30
lohxt1
f09fd19ca0 chore: updates 2024-12-30 16:38:41 +05:30
lohxt1
f578c188fb chore: updates 2024-12-30 16:32:46 +05:30
lohxt1
0d2b449b27 chore: aikido -- library version upgrades 2024-12-30 16:26:38 +05:30
lohit
e897dc1eb0 Merge pull request #3706 from Pragadesh-45/fix/response-format-updates
fix: improve JSON parsing logic for `bruno-cli`
2024-12-30 12:44:14 +05:30
lohit
85b6cae03d Merge pull request #3709 from usebruno/fix/cli-gh-workflow
fix: added input block to the cli workflow
2024-12-30 11:30:44 +05:30
lohit
d215cf740b fix: added input block to the cli workflow 2024-12-30 11:30:30 +05:30
Sanjai Kumar
4b277aa874 Merge branch 'usebruno:main' into handle-invalid-auth-in-pm-export 2024-12-30 11:07:23 +05:30
Sanjai Kumar
e9378d7895 fix: handle unsupported auth mode by returning 'noauth' type 2024-12-30 11:04:41 +05:30
lohit
78aa0d07ae Merge pull request #3707 from lohxt1/fix/multipart-tests
fix: multipart tests
2024-12-30 10:49:35 +05:30
lohit
e4574e3a56 Merge pull request #3701 from lohxt1/fix/cli-multipart
fix: cli multipart
2024-12-30 10:48:54 +05:30
lohxt1
795cd196f2 fix: multipart tests 2024-12-30 10:46:12 +05:30
Pragadesh-45
7b935bd206 fix: improve JSON parsing logic 2024-12-30 10:05:23 +05:30
lohxt1
43e892f9b0 fix: cli multipart 2024-12-27 16:50:12 +05:30
lohit
1f2bee1f90 Merge pull request #3676 from pooja-bruno/fix/string-json-response
fix: string json response error
2024-12-27 16:30:32 +05:30
Pooja Belaramani
767db75730 update: comment 2024-12-27 16:10:57 +05:30
Pooja Belaramani
b6b4b7362f fix: condition 2024-12-27 15:51:06 +05:30
Pooja Belaramani
54d8fbc478 rm: json stringfy from json string data res 2024-12-27 11:16:26 +05:30
Pooja Belaramani
9a2d8bfff3 adding quotes in text response 2024-12-27 11:12:46 +05:30
Pragadesh-45
907f6a19ad revert: digest auth testbench 2024-12-26 10:40:51 +05:30
Pragadesh-45
b612da4f3c fix: updates 2024-12-26 08:17:43 +05:30
Pragadesh-45
625140d1f4 fix: enhance digest authentication 2024-12-26 08:10:40 +05:30
Pragadesh-45
284519cd43 fix: improve digest authorization header opaque 2024-12-26 07:51:55 +05:30
Pragadesh-45
26daee5d98 fix: improve parsing of authentication details in digest interceptor 2024-12-24 15:37:59 +05:30
Pooja Belaramani
87988b6879 fix: tooltip for env name and rm trash overflow changes 2024-12-24 12:57:41 +05:30
pooja-bruno
f8711a91d9 fix: plain text curl data (#3677) 2024-12-23 17:29:06 +05:30
Pooja Belaramani
582e8e5eac fix: condition 2024-12-23 13:36:58 +05:30
Pooja Belaramani
236bc48d98 fix: null data response 2024-12-23 12:11:46 +05:30
Pooja Belaramani
a63afd6c0b fix: asset value cursor issue while editing 2024-12-19 16:04:52 +05:30
Pragadesh-45
fec99f0780 Merge branch 'usebruno:main' into main 2024-12-19 11:05:27 +05:30
Pooja Belaramani
bf142af6d9 fix: env name truncate 2024-12-19 11:00:51 +05:30
Pragadesh-45
57a85e535c fix: add lodash import for utility functions 2024-12-18 19:03:19 +05:30
lohxt1
f72d643e02 feat: updates 2024-12-18 18:57:23 +05:30
Pooja Belaramani
aea25842ce fix: string json response error 2024-12-18 15:01:06 +05:30
Pooja Belaramani
0831b610cf fix: assertion clear value 2024-12-17 21:19:34 +05:30
Anoop M D
f871bc0fa2 feat: setup script 2024-12-17 13:46:48 +05:30
lohit
db90d31b3f Merge pull request #3670 from lohxt1/fix/validations
fix: validations
2024-12-17 11:14:42 +05:30
lohxt1
e44dcad01a fix: updates 2024-12-16 22:13:26 +05:30
lohxt1
7a8d1624d1 fix: updates 2024-12-16 22:09:10 +05:30
lohxt1
0ad0af041b fix: validations updates 2024-12-16 18:46:16 +05:30
Pooja Belaramani
ad59e3f8d1 fix: env names wrap and trash icon overflow 2024-12-16 18:10:33 +05:30
lohit
b9ec0acab4 Merge pull request #3662 from lohxt1/revert/rename-item-watcher-fix
revert: rename-item watcher fix -- tbd
2024-12-16 14:53:12 +05:30
lohit
0d126abfbd Merge branch 'main' into revert/rename-item-watcher-fix 2024-12-16 14:52:52 +05:30
lohxt1
a5096ce413 revert: rename-item watcher fix -- tbd 2024-12-16 14:51:02 +05:30
lohit
3bf98aab3b Merge pull request #3661 from Pragadesh-45/main
fix: update condition for renaming items in Windows OS
2024-12-16 12:12:24 +05:30
Pragadesh-45
9b83cd7b84 fix: update condition for renaming items in Windows OS 2024-12-16 12:01:42 +05:30
lohit
21f9e80706 Merge pull request #3660 from lohxt1/fix/add-tests-scripts-to-request
fix: add tests to request object
2024-12-16 10:14:24 +05:30
lohxt1
e8bc32b39b fix: add tests to request object 2024-12-16 10:12:25 +05:30
lohit
380047e025 Merge pull request #1043 from nelup20/bugfix/521-error_accessing_response_property_with_context_as_key
fix(#521): Allow "context" as the name of a key/var in a JS expression
2024-12-15 20:01:33 +05:30
lohit
434ae6c70f _BrunoNewFunctionInnerContext to __bruno__functionInnerContext 2024-12-15 20:00:57 +05:30
lohit
22612a7dbe _BrunoNewFunctionInnerContext to __bruno__functionInnerContext 2024-12-15 20:00:13 +05:30
lohit
06c0b7c78a Merge pull request #3659 from lohxt1/followup-pr/close-tab-after-rename-folders-only
fix: followup pr - only close folder tab on rename pr-3607
2024-12-15 19:56:32 +05:30
lohxt1
c154dec2b5 fix: followup pr - only close folder tab on rename 2024-12-15 19:55:15 +05:30
lohit
3abe611752 Merge pull request #3658 from lohxt1/followup-pr/multipart-content-type
refactor: multipart content-type pr review fixes
2024-12-15 19:15:00 +05:30
lohxt1
f3cfacdd43 fix: updates 2024-12-15 19:11:19 +05:30
lohit
af4b2105be Merge pull request #3657 from lohxt1/followup-pr/multipart-content-type
refactor: multipart content-type pr-2121
2024-12-15 18:25:01 +05:30
lohxt1
7ae64605c2 chore: updates 2024-12-15 18:19:55 +05:30
lohit
83bbbe3fb3 Merge pull request #2121 from end3rbyte/feature/1602-multipart-content-type
feature: Multi-part requests: user should be able to set content-type for each part in a multi-part request. #1602
2024-12-15 18:08:44 +05:30
lohit
73ea5f155d Merge branch 'main' into feature/1602-multipart-content-type 2024-12-15 18:08:05 +05:30
lohit
366bd99e92 Merge pull request #3642 from lohxt1/fix/remove-jsonbigint-from-cli
fix: cli -- json response parsing
2024-12-15 18:00:45 +05:30
lohit
6b6fc9a3dc Merge branch 'main' into fix/remove-jsonbigint-from-cli 2024-12-15 18:00:38 +05:30
lohit
6323b54c38 Merge pull request #3656 from sanjaikumar-bruno/move-createFormData-function
refactor createFormData function
2024-12-15 17:57:39 +05:30
Sanjai Kumar
57e6af703c refactor createFormData to handle data as an array of objects and improve file handling 2024-12-15 17:55:52 +05:30
lohit
09120a96e8 Merge pull request #3586 from sanjaikumar-bruno/fix/multipart-file-with-same-key-name
fix: refactor createFormData function
2024-12-15 17:38:49 +05:30
lohit
f1e6d5eefe add createFormData import from utils 2024-12-15 17:38:22 +05:30
lohit
ba41f17439 Merge branch 'main' into fix/multipart-file-with-same-key-name 2024-12-15 17:34:39 +05:30
lohit
d37e9aaafa Merge pull request #3522 from sanjai0py/feat/add-isNotEmpty-assertion
feat: add 'isNotEmpty' assertion operator
2024-12-15 17:28:23 +05:30
lohit
5a9bda2a0f use to.not.be.empty 2024-12-15 17:27:47 +05:30
Sanjai Kumar
eaa4f4e57b Fix/global env duplicate name error (#3590)
* fix: enhance environment name validation for global environments

* fix: improve environment name validation logic in CreateEnvironment component

* Update index.js

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-12-15 16:47:08 +05:30
_Pragadesh M
8992a457a8 Feat/displaying variable hints as secrets for Global Environment Secrets (#3591)
* feat: added new utility function `getEnvironmentVariablesMasked`

* refactor: Update `getAllVariables` function in `collections/index.js`

* refactor: Update `brunoVarInfo.js` to handle masked environment variables

* code cleanup

* feat: Add masked global environment variables functionality

* refactor: Rename variables for clarity in RequestTabPanel component

* feat: updates

* Update index.js

---------

Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
2024-12-15 16:45:54 +05:30
Pragadesh-45
b181aba646 feat: enhance collection item deletion and renaming functionality (#3607) 2024-12-15 16:44:32 +05:30
Pragadesh-45
47179535d5 fix: update content-type header for XML requests to application/xml (#3648) 2024-12-15 16:40:12 +05:30
Pragadesh-45
316b632338 refactor: add sanitization for collection names and improve directory name handling (#3559) 2024-12-15 16:38:56 +05:30
Filip Gaľa
dc469afeea feat: folder documentation (#3206)
* add docs, save not working yet

* working folder docs

* revert unrelated changes

* prettier fix

* allow save folder with command

* include folder docs in `bruno-collection` json export

* docs

---------

Co-authored-by: Filip Gala <filip.gala@student.tuke.sk>
Co-authored-by: lohit <lohxt.space@gmail.com>
2024-12-15 16:33:30 +05:30
lohit
ee715a6dc6 chore: headers/vars/script merge fns refactor (#3654)
* chore: cli code refactoring
* chore: code refactoring
2024-12-15 15:40:49 +05:30
lohit
19ad0ecef7 fix: rsbuild related error fixes (#3655) 2024-12-15 15:30:58 +05:30
lohit
993424a2b8 fix: bruno-app tests (#3653) 2024-12-15 15:30:09 +05:30
Pragadesh-45
086c4c063e fix: update pathname validation to use basename for directory checks #2193 (#3550)
* fix: implement custom pathname validation #2193

* fix: update pathname validation to use basename for directory checks

* fix: improve error message for invalid pathname in collection.js
2024-12-15 15:08:36 +05:30
Sanjai Kumar
a6ac98b709 feat: add support for importing collection-level variables from PM collections (#3481)
* feat: add support for importing collection-level variables from Postman collections

* fix: add optional chaining for collection variables in Postman importer

* Update postman-collection.js

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-12-15 14:53:53 +05:30
Anoop M D
22ecd0284f chore: updated package-lock 2024-12-14 20:29:22 +05:30
lohit
33e86a9097 feat: replace nextjs with rsbuild (#3617)
* poc: bruno app rsbuild
* fix: updates

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-12-14 19:57:04 +05:30
Anoop M D
3efcdf254e fix(#3627): fix rename issues on windows os 2024-12-14 19:42:39 +05:30
lohit
ea1f385d1c fix: rename folder case insensitive (#3635)
* fix: rename folder case insensitive

* fix: add hasSubFolders utility and update folder moving logic for Windows OS

* fix: validations

* fix: updates

* fix: updates

---------

Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
2024-12-14 18:55:28 +05:30
lohit
4dcaaab52c Merge branch 'main' into fix/remove-jsonbigint-from-cli 2024-12-12 17:31:59 +05:30
lohxt1
6326dc3d9c feat: updates 2024-12-12 17:28:15 +05:30
lohxt1
a55ed9bd50 feat: cli -- system level proxy fix 2024-12-12 14:09:06 +05:30
lohit
57d86eb118 Merge pull request #3624 from lohxt1/fix/cli-brutojson
fix: cli -- brutojson fn -- req script should be obj by default
2024-12-10 13:26:45 +05:30
lohxt1
85c6b2d97f fix: cli -- brutojson fn -- script obj 2024-12-10 12:57:03 +05:30
lohit
0c574aeb1e Merge pull request #3605 from lohxt1/fix/cli-updates
fix: cli - include collection level script, updated testbench
2024-12-09 18:52:23 +05:30
lohxt1
3fe0d43bdc fix: cli - collection level script, added tests 2024-12-06 22:58:02 +05:30
Anoop M D
67ead9739e chore: temporarily reverting npm cli test runs on windows 2024-12-06 19:33:53 +05:30
Anoop M D
36021b5b38 feat: run npm cli tests on macos and windows 2024-12-06 19:23:56 +05:30
lohit
1e45725ba1 Merge pull request #3601 from lohxt1/fix/cli--env
fix: cli -- envs path resolution
2024-12-06 17:51:57 +05:30
lohxt1
d4616c78c8 fix: cli envs path resolution 2024-12-06 17:47:27 +05:30
lohit
5e5656d268 Merge pull request #3598 from Pragadesh-45/feat/folder-recursive-rename-win32
bugfix/folder recursive rename win32 (fixes: #3597) updates: #3236
2024-12-05 23:08:21 +05:30
Pragadesh-45
52e01935f5 Merge remote-tracking branch 'origin/main' into feat/folder-recursive-rename-win32 2024-12-05 17:33:21 +05:30
Pragadesh-45
99f912312d fix: add renameSync 2024-12-05 17:27:24 +05:30
lohit
915ebf3387 Merge pull request #3186 from lohxt1/feat/cli-collection-vars
feat: adds collection/folder/request vars and scripts support to cli
2024-12-05 16:01:06 +05:30
lohxt1
bb18c532da chore: updated lock file 2024-12-05 15:47:41 +05:30
lohxt1
4b4bd3bc95 fix: updates 2024-12-05 15:45:36 +05:30
lohxt1
482cb05d63 fix: updates 2024-12-05 11:42:23 +05:30
lohit
32153c4dbf Merge pull request #3592 from lohxt1/main
chore: version bump
2024-12-04 20:58:12 +05:30
lohxt1
7535b3d4ba chore: version bump 2024-12-04 20:55:31 +05:30
lohxt1
4dd4800ee9 fix: tests 2024-12-04 18:44:20 +05:30
lohxt1
e1ebaabcc7 Merge branch 'main' of lohxt1:lohxt1/bruno into feat/cli-collection-vars 2024-12-04 17:28:55 +05:30
Sanjai Kumar
f110d898f5 fix: refactor createFormData function 2024-12-03 18:01:22 +05:30
Pragadesh-45
5b6172e5ac feat: enhance keyboard navigation for language selection in GenerateCodeItem 2024-12-03 17:59:48 +05:30
lohit
fd22ff8962 Merge pull request #3578 from lohxt1/fix/validate-env-name-validations
fix: validate env name fn null checks
2024-12-02 14:14:53 +05:30
lohit
5f5cc5eb22 fix: validate env name fn null checks 2024-12-02 14:14:10 +05:30
Anoop M D
eb6944a1c9 fix(#163) - removed max limit on request names 2024-12-01 13:52:59 +05:30
Sanjai Kumar
b4ea101350 Fix/xml body not getting added during curl import. (#3547)
* Add support for XML data handling in curl-to-json utility

* Refactor XML content type handling in curl utility
2024-11-30 19:23:39 +05:30
Marc Pfister
0a8217e4ab add "docs" to copy for export (#3551) 2024-11-30 19:19:05 +05:30
Sanjai Kumar
514da55923 Fix/improve env modal error when duplicate names (#3557)
* Enhance environment creation validation and improve error handling

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-30 19:16:09 +05:30
lohit
bd2cf554b6 fix: response pane width shift styling for timeline tab (#3573)
* fix: posthog api key as a process env var
* fix: response pane timeline tab inconsistent width
2024-11-30 18:55:10 +05:30
Pragadesh-45
2f752085f3 fix: humanize-date fixes: #3556 (#3565)
* fix: humanize-date
* fix: improve notification handling and enhance date validation
2024-11-30 18:51:44 +05:30
Pragadesh-45
f2cfcab091 feat: Graceful handle rename/move: Fix EPERM Error When Renaming Parent Folders on Windows (#3236)
* refactor filesystem.js to use isWindowsOS()

* add tempDir logic to gracefully rename the parent folder in a collection. fix: `EPERM`

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-29 12:18:11 +05:30
Anoop M D
4283bb4bb0 Merge branch 'main' into feat/folder-recursive-rename-win32 2024-11-29 12:16:27 +05:30
Pragadesh-45
707cddea90 Fix/invalid file name handling (#3274)
* feat: implement utility function `isValidFilename`
* refactor: added filename validator checks for `rename-item` and `new-request`
* chore: added `fileName.startsWith('.')`
2024-11-29 12:04:59 +05:30
Ryan
39a44e9b4f Update readme.md (#3520) 2024-11-22 17:37:07 +05:30
lohit
fb8c54dd7a Merge pull request #3467 from Pragadesh-45/feat/cli-improvements
feat: add option to omit headers and skip specific headers in CLI run command output
2024-11-21 20:56:16 +05:30
lohit
7400d890e4 Merge branch 'main' into feat/cli-improvements 2024-11-21 20:56:05 +05:30
lohit
3a29e2e333 Merge pull request #3504 from Pragadesh-45/feat/cli-improvements-ssl-related
feat: add --client-cert-config option for secure connections in CLI run command
2024-11-21 20:53:36 +05:30
lohit
1fb4298681 Merge pull request #2820 from matthewdickinson/feature/cli-cookies
Added cookie support to CLI requests
2024-11-21 17:56:29 +05:30
lohit
6d8cc38946 changed use-cookies to disable-cookies 2024-11-21 17:53:09 +05:30
lohit
9d4246d74b changed use-cookies to disable-cookies
support for sending cookies by default
2024-11-21 17:51:18 +05:30
lohit
1238bf7270 Merge pull request #3523 from lohxt1/fix/tests-editior-style-fix
fix: codemirror editors background styling fix
2024-11-21 17:15:50 +05:30
Pragadesh-45
e9d459fa5e feat: create cert cert config cli command 2024-11-21 17:13:52 +05:30
lohxt1
1c4acf7301 fix: codemirror editor background styling fix 2024-11-21 16:50:11 +05:30
lohxt1
6385d00807 fix: codemirror editor background styling fix 2024-11-21 16:49:20 +05:30
Pragadesh-45
157389424d feat: rename CLI option for omitting headers to clarify functionality 2024-11-21 16:28:28 +05:30
Pragadesh-45
1b30229903 Feat/displaying variable hints as secrets (#3268)
* feat: added new utility function `getEnvironmentVariablesMasked`
2024-11-21 15:48:32 +05:30
Pragadesh-45
72bd1b4cbf feat: rename CLI options for omitting and skipping headers to clarify reporter context 2024-11-21 15:36:15 +05:30
Sanjai Kumar
4a4481a26f feat: add 'isNotEmpty' assertion operator 2024-11-21 12:00:25 +05:30
lohit
0bec17facd Merge pull request #3518 from lohxt1/main
chore: update package lock file
2024-11-20 19:57:44 +05:30
lohxt1
1c86b5f340 chore: update package lock file 2024-11-20 19:56:44 +05:30
lohit
917205299a Merge pull request #3517 from lohxt1/fix/video-preview-comp--memo-import
fix: video preview comp memo import
2024-11-20 19:21:41 +05:30
lohxt1
916f28633e fix: video preview comp memo import 2024-11-20 19:20:43 +05:30
lohit
6c2451b6f2 Merge pull request #3516 from lohxt1/fix/server_rendered-logic
fix: server_rendered codemirror logic for newer versions of nextjs
2024-11-20 19:05:20 +05:30
lohxt1
24563bdaaf fix: server_rendered codemirror logic for newer versions of nextjs 2024-11-20 19:03:36 +05:30
lohit
28d30b1ef7 Merge pull request #3515 from lohxt1/feat/delete-env-var--safe-mode-shim
feat: deleteEnvVar safe mode shim
2024-11-20 18:29:54 +05:30
lohxt1
6442e3ceca feat: deleteEnvVar safe mode shim 2024-11-20 18:29:19 +05:30
arshan1019
56c3bf0899 Fix: (#3383) openapi yaml req body not importing (#3459) 2024-11-20 18:16:53 +05:30
Sanjai Kumar
11a3ea9fbb remove error logging for missing global environment (#3447) 2024-11-20 18:16:08 +05:30
Sanjai Kumar
84095a4183 fix/ Script execution is prevented by line comments (#3462)
* refactor: simplify mode settings and update comment toggle functionality
---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 18:14:37 +05:30
Pragadesh-45
d92dd46d4e fix: added missing translation (#3352)
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 18:08:39 +05:30
Pragadesh-45
a752921413 feat: add validation to prevent duplicate global environment names (#3450) 2024-11-20 18:07:37 +05:30
Pragadesh-45
44debfd9b9 fix: improve masking logic in MaskedEditor for large content handling (fixes #2842) (#3472)
* fix: improve masking logic in MaskedEditor for large content handling

* fix: remove comment in MaskedEditor masking logic
2024-11-20 18:06:07 +05:30
Pragadesh-45
278ca8bf29 fix: enhance path normalization for WSL compatibility in watcher (#3482) 2024-11-20 18:02:06 +05:30
Pragadesh-45
23c22a96bc Feat/import translation for deprecated pm import (#3388)
* feat: add translation for pm `tests[]`
* feat: add bru.deleteEnvVar function and update translations

---------

Co-authored-by: Pragadesh-45 <temporary7904@gmail.com>
2024-11-20 17:58:59 +05:30
Sanjai Kumar
77d3fa7e1e bugfix / Update video preview functionality (#3433)
* Add video preview functionality and update dependencies
* Refactor video preview component to use Buffer for base64 decoding and update muted prop syntax

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 17:34:00 +05:30
Sanjai Kumar
412a0ed078 Now based on the request type appropriate views are shown. (#3340)
* Now based on the request type appropriate views are shown.

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 17:22:04 +05:30
lohit
1cb0d4e191 chore: node version bump -- v22.11.0 (#3508)
node version bump with updates to cipher logic
2024-11-20 17:09:02 +05:30
lohit
aff7c405cd fix: import openapi -- baseUrl env value should not include trailing slash (#3440)
* fix: openapi baseUrl env value should remove trailing slash

* feat: updates
2024-11-20 03:38:59 +05:30
lohit
59108472a2 fix: codemirror styling updates (#3439) 2024-11-20 03:35:55 +05:30
lohit
c4492b5d94 fix: exclude Meta, Alt, Home and End key press for autocomplete trigger (#3441) 2024-11-20 03:31:44 +05:30
lohit
7fd7eafdcb fix: incorrect call of dispatch fn (#3452) 2024-11-20 03:30:50 +05:30
Pragadesh-45
41040bc296 fix: update image URL and adjust tests for age variable in bruno.bru files (#3483)
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 03:29:27 +05:30
lohit
ad5b625655 fix: checkov CKV_GHA_7 warning (#3489)
* fix: posthog api key as a process env var

* fix: checkov bru cli workflow warning

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2024-11-20 03:26:22 +05:30
lohit
cd629451e4 fix: checkov CKV2_GHA_1 warning (#3488)
* fix: posthog api key as a process env var

* fix: checkov tests workflow warning

* revert: updates
2024-11-20 03:25:32 +05:30
lohit
dc77ee7c04 feat: moved posthog key to process env (#3490) 2024-11-20 03:24:29 +05:30
lohit
c322baa9c8 fix: server_rendered logic for newer versions of nextjs (#3509)
* fix: server_rendered condition update
2024-11-20 03:22:39 +05:30
lohxt1
b206b70d2e feat: fix tests 2024-11-19 12:36:20 +05:30
Pragadesh-45
9a325caeee feat: add ssl-cert-list option for secure connections in CLI run command 2024-11-19 10:22:55 +05:30
lohxt1
d0ef70473d fix: remove json bigint from cli 2024-11-18 13:39:55 +05:30
Anoop M D
4894ac2754 fix: fixed failing test 2024-11-15 19:26:30 +05:30
lohxt1
df206dc4d9 fix: posthog api key as a process env var 2024-11-15 10:38:16 +05:30
Pragadesh-45
45cc97ee20 feat: add option to skip specific headers in CLI run command output 2024-11-13 14:51:48 +05:30
Pragadesh-45
642413e35c Merge remote-tracking branch 'origin/main' into feat/cli-improvements 2024-11-13 11:37:29 +05:30
Pragadesh-45
abb6490232 feat: add option to omit headers in CLI run command output 2024-11-12 13:27:58 +05:30
ganesh
40001949b8 Add a proper example for using the usebruno query library in the README file. (#3464) 2024-11-12 07:06:11 +05:30
lohit
bdfe9c16f1 fix: cli run-summary count fix for requests with ECONNREFUSED error (#3451)
* fix: cli run summary for requests with ECONNREFUSED error
* feat: updates
2024-11-08 16:57:37 +05:30
Matthew Dickinson
b82a2c3312 Added cookie support to CLI requests 2024-10-18 17:48:35 -04:00
Pragadesh-45
ca6c2ebb03 style: add logic to handle different screen sizes 2024-10-16 22:05:38 +05:30
Pragadesh-45
b900d3070d feat: tab switch for languages 2024-10-15 14:16:51 +05:30
Pragadesh-45
a0fcb6c91f refactor: GenerateCodeItem component to fix width issue 2024-10-15 14:15:06 +05:30
Pragadesh-45
1fe7af4fad add tempDir logic to gracefully rename the parent folder in a collection. fix: EPERM 2024-10-01 15:00:55 +05:30
Pragadesh-45
de2053f988 refactor filesystem.js to use isWindowsOS() 2024-10-01 14:57:19 +05:30
lohxt1
1c110f0cb0 feat: removed console 2024-09-24 21:51:18 +05:30
lohxt1
5fd6773f43 feat: added support for collection/folder/request scripts 2024-09-24 21:49:49 +05:30
lohxt1
9c2c86baf6 feat: add collection vars, folder vars, request vars support to cli 2024-09-24 19:09:47 +05:30
busy-panda
62babef678 Merge branch 'main' into feature/1602-multipart-content-type
# Conflicts:
#	packages/bruno-lang/v2/src/jsonToBru.js
2024-09-09 11:49:38 +02:00
busy-panda
a703b84681 Merge branch 'main' into feature/1602-multipart-content-type
# Conflicts:
#	packages/bruno-lang/v2/src/bruToJson.js
#	packages/bruno-lang/v2/src/jsonToBru.js
2024-07-01 15:17:53 +02:00
busy-panda🐼🐼
9ba03a5f02 Merge branch 'usebruno:main' into feature/1602-multipart-content-type 2024-05-01 09:19:08 +02:00
busy-panda🐼🐼
cdf56fcec1 Merge branch 'usebruno:main' into feature/1602-multipart-content-type 2024-04-24 11:47:07 +02:00
busy-panda🐼🐼
f27e79cb01 Merge branch 'usebruno:main' into feature/1602-multipart-content-type 2024-04-20 10:25:02 +02:00
busy-panda
40872f6e9e Reduced the width of the Operator column in tab Assert 2024-04-19 17:14:44 +02:00
busy-panda
b7f4edac24 Reduced the width of the Operator column in tab Assert 2024-04-19 17:08:38 +02:00
busy-panda
8e99ed3258 moved assertions from Tests panel to Assert panel and 2024-04-19 17:06:49 +02:00
busy-panda
c3c91d61c8 added placeholder support to MultiLineEditor component 2024-04-19 15:01:41 +02:00
busy-panda
39f60daca7 feature: Multi-part requests: user should be able to set content-type for each part in a multi-part request. #1602 2024-04-18 15:43:09 +02:00
Nelu Platonov
dce1481185 fix(#521): Allow "context" as the name of a key/var in a JS expression 2023-11-23 23:38:43 +01:00
263 changed files with 13427 additions and 3886 deletions

View File

@@ -20,7 +20,10 @@ permissions:
jobs:
test:
name: CLI Tests
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3

View File

@@ -10,6 +10,8 @@ jobs:
name: Unit Tests
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -49,6 +51,10 @@ jobs:
cli-test:
name: CLI Tests
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4

2
.nvmrc
View File

@@ -1 +1 @@
v20.9.0
v22.11.0

8396
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,11 +30,13 @@
"ts-jest": "^29.0.5"
},
"scripts": {
"setup": "node ./scripts/setup.js",
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
@@ -51,6 +53,6 @@
"prepare": "husky install"
},
"overrides": {
"rollup":"3.29.5"
"rollup": "3.29.5"
}
}

View File

@@ -1,4 +1,4 @@
{
"presets": ["next/babel"],
"presets": ["@babel/preset-env"],
"plugins": [["styled-components", { "ssr": true }]]
}

View File

@@ -31,4 +31,6 @@ yarn-error.log*
# next.js
.next/
out/
dist/
.env

View File

@@ -0,0 +1,16 @@
module.exports = {
rootDir: '.',
moduleNameMapper: {
'^assets/(.*)$': '<rootDir>/src/assets/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^hooks/(.*)$': '<rootDir>/src/hooks/$1',
'^themes/(.*)$': '<rootDir>/src/themes/$1',
'^api/(.*)$': '<rootDir>/src/api/$1',
'^pageComponents/(.*)$': '<rootDir>/src/pageComponents/$1',
'^providers/(.*)$': '<rootDir>/src/providers/$1',
'^utils/(.*)$': '<rootDir>/src/utils/$1'
},
clearMocks: true,
moduleDirectories: ['node_modules', 'src'],
testEnvironment: 'node'
};

View File

@@ -1,22 +0,0 @@
module.exports = {
output: 'export',
reactStrictMode: false,
publicRuntimeConfig: {
CI: process.env.CI,
PLAYWRIGHT: process.env.PLAYWRIGHT,
ENV: process.env.ENV
},
webpack: (config, { isServer }) => {
// Fixes npm packages that depend on `fs` module
if (!isServer) {
config.resolve.fallback.fs = false;
}
Object.defineProperty(config, 'devtool', {
get() {
return 'source-map';
},
set() {},
});
return config;
},
};

View File

@@ -3,15 +3,15 @@
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "cross-env ENV=dev next dev -p 3000",
"build": "next build",
"start": "next start",
"lint": "next lint",
"dev": "rsbuild dev",
"build": "rsbuild build -m production",
"preview": "rsbuild preview",
"test": "jest",
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
},
"dependencies": {
"@babel/preset-env": "^7.26.0",
"@fontsource/inter": "^5.0.15",
"@prantlf/jsonlint": "^16.0.0",
"@reduxjs/toolkit": "^1.8.0",
@@ -20,7 +20,6 @@
"@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.7.0",
"axios": "1.7.5",
"classnames": "^2.3.1",
"codemirror": "5.65.2",
"codemirror-graphql": "2.1.1",
@@ -34,22 +33,21 @@
"graphiql": "3.7.1",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"httpsnippet": "^3.0.6",
"i18next": "^23.14.0",
"httpsnippet": "^3.0.9",
"i18next": "24.1.2",
"idb": "^7.0.0",
"immer": "^9.0.15",
"jsesc": "^3.0.2",
"jshint": "^2.13.6",
"json5": "^2.2.3",
"jsonc-parser": "^3.2.1",
"jsonpath-plus": "10.1.0",
"jsonpath-plus": "10.2.0",
"know-your-http-well": "^0.5.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.2",
"markdown-it-replace-link": "^1.2.0",
"mousetrap": "^1.6.5",
"nanoid": "3.3.4",
"next": "14.2.16",
"nanoid": "3.3.8",
"path": "^0.12.7",
"pdfjs-dist": "4.4.168",
"platform": "^1.3.6",
@@ -57,16 +55,17 @@
"prettier": "^2.7.1",
"qs": "^6.11.0",
"query-string": "^7.0.1",
"react": "18.2.0",
"react": "19.0.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-dom": "19.0.0",
"react-hot-toast": "^2.4.0",
"react-i18next": "^15.0.1",
"react-inspector": "^6.0.2",
"react-pdf": "9.1.1",
"react-redux": "^7.2.6",
"react-player": "^2.16.0",
"react-redux": "^7.2.9",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
"strip-json-comments": "^5.0.1",
@@ -78,13 +77,14 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-transform-spread": "^7.16.7",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.16.3",
"@rsbuild/core": "^1.1.2",
"@rsbuild/plugin-babel": "^1.0.3",
"@rsbuild/plugin-node-polyfill": "^1.2.0",
"@rsbuild/plugin-react": "^1.0.7",
"@rsbuild/plugin-sass": "^1.1.0",
"@rsbuild/plugin-styled-components": "1.1.0",
"autoprefixer": "10.4.20",
"babel-loader": "^8.2.3",
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"cross-env": "^7.0.3",
"css-loader": "7.1.2",
"file-loader": "^6.2.0",

View File

@@ -0,0 +1,39 @@
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginBabel } from '@rsbuild/plugin-babel';
import { pluginStyledComponents } from '@rsbuild/plugin-styled-components';
import { pluginSass } from '@rsbuild/plugin-sass';
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'
export default defineConfig({
plugins: [
pluginNodePolyfill(),
pluginReact(),
pluginStyledComponents(),
pluginSass(),
pluginBabel({
include: /\.(?:js|jsx|tsx)$/,
babelLoaderOptions(opts) {
opts.plugins?.unshift('babel-plugin-react-compiler');
}
})
],
source: {
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file
},
html: {
title: 'Bruno'
},
tools: {
rspack: {
module: {
parser: {
javascript: {
// This loads the JavaScript contents from a library along with the main JavaScript bundle.
dynamicImportMode: "eager",
},
},
},
},
}
});

View File

@@ -8,6 +8,8 @@ const StyledWrapper = styled.div`
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
line-break: anywhere;
flex: 1 1 0;
display: flex;
flex-direction: column-reverse;
}
/* Removes the glow outline around the folded json */
@@ -26,6 +28,16 @@ const StyledWrapper = styled.div`
.CodeMirror-dialog {
overflow: visible;
position: relative;
top: unset;
left: unset;
input {
background: transparent;
border: 1px solid #d3d6db;
outline: none;
border-radius: 0px;
}
}
#search-results-count {
@@ -82,6 +94,14 @@ const StyledWrapper = styled.div`
.CodeMirror-search-hint {
display: inline;
}
.cm-s-default span.cm-property {
color: #1f61a0 !important;
}
.cm-s-default span.cm-variable {
color: #397d13 !important;
}
`;
export default StyledWrapper;

View File

@@ -7,15 +7,15 @@
import React from 'react';
import { isEqual, escapeRegExp } from 'lodash';
import { getEnvironmentVariables } from 'utils/collections';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
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';
let CodeMirror;
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
const TAB_SIZE = 2;
if (!SERVER_RENDERED) {
@@ -58,13 +58,14 @@ if (!SERVER_RENDERED) {
'req.getExecutionMode()',
'bru',
'bru.cwd()',
'bru.getEnvName(key)',
'bru.getEnvName()',
'bru.getProcessEnv(key)',
'bru.hasEnvVar(key)',
'bru.getEnvVar(key)',
'bru.getFolderVar(key)',
'bru.getCollectionVar(key)',
'bru.setEnvVar(key,value)',
'bru.deleteEnvVar(key)',
'bru.hasVar(key)',
'bru.getVar(key)',
'bru.setVar(key,value)',
@@ -73,9 +74,16 @@ if (!SERVER_RENDERED) {
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()',
'bru.getRequestVar(key)',
'bru.runRequest(requestPathName)',
'bru.getAssertionResults()',
'bru.getTestResults()',
'bru.sleep(ms)',
'bru.getGlobalEnvVar(key)',
'bru.setGlobalEnvVar(key, value)'
'bru.setGlobalEnvVar(key, value)',
'bru.runner',
'bru.runner.setNextRequest(requestName)',
'bru.runner.skipRequest()',
'bru.runner.stopExecution()',
];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor();
@@ -97,7 +105,7 @@ if (!SERVER_RENDERED) {
if (curWordBru) {
hintWords.forEach((h) => {
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
result.list.push(curWordBru.includes('.') ? h.split('.')[1] : h);
result.list.push(curWordBru.includes('.') ? h.split('.')?.at(-1) : h);
}
});
result.list?.sort();
@@ -189,31 +197,19 @@ export default class CodeEditor extends React.Component {
'Cmd-Y': 'foldAll',
'Ctrl-I': 'unfoldAll',
'Cmd-I': 'unfoldAll',
'Cmd-/': (cm) => {
// comment/uncomment every selected line(s)
const selections = cm.listSelections();
selections.forEach((range) => {
for (let i = range.from().line; i <= range.to().line; i++) {
const selectedLine = cm.getLine(i);
// if commented line, remove comment
if (selectedLine.trim().startsWith('//')) {
cm.replaceRange(
selectedLine.replace(/^(\s*)\/\/\s?/, '$1'),
{ line: i, ch: 0 },
{ line: i, ch: selectedLine.length }
);
continue;
}
// otherwise add comment
cm.replaceRange(
selectedLine.search(/\S|$/) >= TAB_SIZE
? ' '.repeat(TAB_SIZE) + '// ' + selectedLine.trim()
: '// ' + selectedLine,
{ line: i, ch: 0 },
{ line: i, ch: selectedLine.length }
);
}
});
'Ctrl-/': () => {
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
} else {
this.editor.toggleComment();
}
},
'Cmd-/': () => {
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
} else {
this.editor.toggleComment();
}
}
},
foldOptions: {
@@ -281,9 +277,9 @@ export default class CodeEditor extends React.Component {
while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end;
while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start;
let curWord = start != end && currentLine.slice(start, end);
//Qualify if autocomplete will be shown
// Qualify if autocomplete will be shown
if (
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|\s)\w*/.test(event.key) &&
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Meta|Alt|Home|End\s)\w*/.test(event.key) &&
curWord.length > 0 &&
!/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) &&
/(?<!\d)[a-zA-Z\._]$/.test(curWord)
@@ -312,7 +308,7 @@ export default class CodeEditor extends React.Component {
}
if (this.editor) {
let variables = getEnvironmentVariables(this.props.collection);
let variables = getAllVariables(this.props.collection, this.props.item);
if (!isEqual(variables, this.variables)) {
this.addOverlay();
}
@@ -352,7 +348,7 @@ export default class CodeEditor extends React.Component {
addOverlay = () => {
const mode = this.props.mode || 'application/ld+json';
let variables = getEnvironmentVariables(this.props.collection);
let variables = getAllVariables(this.props.collection, this.props.item);
this.variables = variables;
defineCodeMirrorBrunoVariablesMode(variables, mode);

View File

@@ -79,6 +79,15 @@ const AuthMode = ({ collection }) => {
>
Digest Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('ntlm');
}}
>
NTLM Auth
</div>
<div
className="dropdown-item"
onClick={() => {

View File

@@ -0,0 +1,17 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
max-width: 400px;
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -0,0 +1,110 @@
import React from 'react';
import get from 'lodash/get';
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 StyledWrapper from './StyledWrapper';
const NTLMAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const ntlmAuth = get(collection, 'root.request.auth.ntlm', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateCollectionAuth({
mode: 'ntlm',
collectionUid: collection.uid,
content: {
username: username,
password: ntlmAuth.password,
domain: ntlmAuth.domain
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateCollectionAuth({
mode: 'ntlm',
collectionUid: collection.uid,
content: {
username: ntlmAuth.username,
password: password,
domain: ntlmAuth.domain
}
})
);
};
const handleDomainChange = (domain) => {
dispatch(
updateCollectionAuth({
mode: 'ntlm',
collectionUid: collection.uid,
content: {
username: ntlmAuth.username,
password: ntlmAuth.password,
domain: domain
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={ntlmAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
collection={collection}
isSecret={true}
/>
</div>
<label className="block font-medium mb-2">Domain</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.domain || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleDomainChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default NTLMAuth;

View File

@@ -1,5 +1,7 @@
import styled from 'styled-components';
const Wrapper = styled.div``;
const Wrapper = styled.div`
max-width: 800px;
`;
export default Wrapper;

View File

@@ -11,6 +11,8 @@ import ApiKeyAuth from './ApiKeyAuth/';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import OAuth2 from './OAuth2';
import NTLMAuth from './NTLMAuth';
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
@@ -32,6 +34,9 @@ const Auth = ({ collection }) => {
case 'digest': {
return <DigestAuth collection={collection} />;
}
case 'ntlm': {
return <NTLMAuth collection={collection} />;
}
case 'oauth2': {
return <OAuth2 collection={collection} />;
}

View File

@@ -68,12 +68,13 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
});
const getFile = (e) => {
if (e.files?.[0]?.path) {
const filePath = window?.ipcRenderer?.getFilePath(e?.files?.[0]);
if (filePath) {
let relativePath;
if (isWindowsOS()) {
relativePath = slash(path.win32.relative(root, e.files[0].path));
relativePath = slash(path.win32.relative(root, filePath));
} else {
relativePath = path.posix.relative(root, e.files[0].path);
relativePath = path.posix.relative(root, filePath);
}
formik.setFieldValue(e.name, relativePath);
}

View File

@@ -2,16 +2,12 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 240px);
.CodeMirror-scroll {
padding-bottom: 0px;
}
}
.editing-mode {
cursor: pointer;
color: ${(props) => props.theme.colors.text.yellow};
}
`;

View File

@@ -8,6 +8,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
@@ -29,19 +30,50 @@ const Docs = ({ collection }) => {
);
};
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleDiscardChanges = () => {
dispatch(
updateCollectionDocs({
collectionUid: collection.uid,
docs: docs
})
);
toggleViewMode();
}
const onSave = () => {
dispatch(saveCollectionRoot(collection.uid));
toggleViewMode();
}
return (
<StyledWrapper className="mt-1 h-full w-full relative">
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
{isEditing ? 'Preview' : 'Edit'}
<StyledWrapper className="mt-1 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'>
<IconFileText size={20} strokeWidth={1.5} />
Documentation
</div>
<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}>
Save
</button>
</>
) : (
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
</div>
)}
</div>
</div>
{isEditing ? (
<CodeEditor
collection={collection}
theme={displayedTheme}
value={docs || ''}
value={docs}
onEdit={onEdit}
onSave={onSave}
mode="application/text"
@@ -49,10 +81,44 @@ const Docs = ({ collection }) => {
fontSize={get(preferences, 'font.codeFontSize')}
/>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
<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} />
}
</div>
</div>
)}
</StyledWrapper>
);
};
export default Docs;
const documentationPlaceholder = `
Welcome to your collection documentation! This space is designed to help you document your API collection effectively.
## Overview
Use this section to provide a high-level overview of your collection. You can describe:
- The purpose of these API endpoints
- Key features and functionalities
- Target audience or users
## Best Practices
- Keep documentation up to date
- Include request/response examples
- Document error scenarios
- Add relevant links and references
## Markdown Support
This documentation supports Markdown formatting! You can use:
- **Bold** and *italic* text
- \`code blocks\` and syntax highlighting
- Tables and lists
- [Links](https://example.com)
- And more!
`;

View File

@@ -1,6 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
max-width: 800px;
table {
width: 100%;
border-collapse: collapse;

View File

@@ -1,39 +0,0 @@
import React from 'react';
import StyledWrapper from './StyledWrapper';
import { getTotalRequestCountInCollection } from 'utils/collections/';
const Info = ({ collection }) => {
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
return (
<StyledWrapper className="w-full flex flex-col h-full">
<div className="text-xs mb-4 text-muted">General information about the collection.</div>
<table className="w-full border-collapse">
<tbody>
<tr className="">
<td className="py-2 px-2 text-right">Name&nbsp;:</td>
<td className="py-2 px-2">{collection.name}</td>
</tr>
<tr className="">
<td className="py-2 px-2 text-right">Location&nbsp;:</td>
<td className="py-2 px-2 break-all">{collection.pathname}</td>
</tr>
<tr className="">
<td className="py-2 px-2 text-right">Ignored files&nbsp;:</td>
<td className="py-2 px-2 break-all">{collection.brunoConfig?.ignore?.map((x) => `'${x}'`).join(', ')}</td>
</tr>
<tr className="">
<td className="py-2 px-2 text-right">Environments&nbsp;:</td>
<td className="py-2 px-2">{collection.environments?.length || 0}</td>
</tr>
<tr className="">
<td className="py-2 px-2 text-right">Requests&nbsp;:</td>
<td className="py-2 px-2">{totalRequestsInCollection}</td>
</tr>
</tbody>
</table>
</StyledWrapper>
);
};
export default Info;

View File

@@ -0,0 +1,56 @@
import React from 'react';
import { getTotalRequestCountInCollection } from 'utils/collections/';
import { IconFolder, IconFileOff, IconWorld, IconApi } from '@tabler/icons';
const Info = ({ collection }) => {
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
return (
<div className="w-full flex flex-col h-fit">
<div className="rounded-lg py-6">
<div className="grid gap-6">
{/* 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>
<div className="ml-4">
<div className="font-semibold text-sm">Location</div>
<div className="mt-1 text-sm text-muted break-all">
{collection.pathname}
</div>
</div>
</div>
{/* 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>
<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>
</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>
<div className="ml-4">
<div className="font-semibold text-sm">Requests</div>
<div className="mt-1 text-sm text-muted">
{totalRequestsInCollection} request{totalRequestsInCollection !== 1 ? 's' : ''} in collection
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Info;

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
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-left-radius: 3px;
border-top-right-radius: 3px;
}
.table {
thead {
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
}
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { flattenItems } from "utils/collections";
import { IconAlertTriangle } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper";
const RequestsNotLoaded = ({ collection }) => {
const flattenedItems = flattenItems(collection.items);
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
if (!itemsFailedLoading?.length) {
return null;
}
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" />
<span className="font-medium">Following requests were not loaded</span>
</div>
<table className="w-full border-collapse">
<thead>
<tr>
<th className="py-2 px-3 text-left font-medium">
Pathname
</th>
<th className="py-2 px-3 text-left font-medium">
Size
</th>
</tr>
</thead>
<tbody>
{flattenedItems?.map((item, index) => (
item?.partial && !item?.loading ? (
<tr key={index}>
<td className="py-1.5 px-3">
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
</td>
<td className="py-1.5 px-3">
{item?.size?.toFixed?.(2)}&nbsp;MB
</td>
</tr>
) : null
))}
</tbody>
</table>
</StyledWrapper>
);
};
export default RequestsNotLoaded;

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.partial {
color: ${(props) => props.theme.colors.text.yellow};
opacity: 0.8;
}
.loading {
color: ${(props) => props.theme.colors.text.muted};
opacity: 0.8;
}
.completed {
color: ${(props) => props.theme.colors.text.green};
opacity: 0.8;
}
.failed {
color: ${(props) => props.theme.colors.text.danger};
opacity: 0.8;
}
`;
export default StyledWrapper;

View File

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

View File

@@ -1,6 +1,8 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
.settings-label {
width: 110px;
}

View File

@@ -1,6 +1,8 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
div.CodeMirror {
height: inherit;
}

View File

@@ -1,8 +1,6 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
div.tabs {
div.tab {
padding: 6px 0px;

View File

@@ -1,5 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div``;
const StyledWrapper = styled.div`
max-width: 800px;
`;
export default StyledWrapper;

View File

@@ -1,6 +1,8 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
div.title {
color: var(--color-tab-inactive);
}

View File

@@ -12,12 +12,11 @@ import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
import Test from './Tests';
import Docs from './Docs';
import Presets from './Presets';
import Info from './Info';
import StyledWrapper from './StyledWrapper';
import Vars from './Vars/index';
import DotIcon from 'components/Icons/Dot';
import Overview from './Overview/index';
const ContentIndicator = () => {
return (
@@ -97,6 +96,9 @@ const CollectionSettings = ({ collection }) => {
const getTabPanel = (tab) => {
switch (tab) {
case 'overview': {
return <Overview collection={collection} />;
}
case 'headers': {
return <Headers collection={collection} />;
}
@@ -128,12 +130,6 @@ const CollectionSettings = ({ collection }) => {
/>
);
}
case 'docs': {
return <Docs collection={collection} />;
}
case 'info': {
return <Info collection={collection} />;
}
}
};
@@ -146,6 +142,9 @@ const CollectionSettings = ({ collection }) => {
return (
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('overview')} role="tab" onClick={() => setTab('overview')}>
Overview
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
@@ -177,13 +176,6 @@ const CollectionSettings = ({ collection }) => {
Client Certificates
{clientCertConfig.length > 0 && <ContentIndicator />}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs
{hasDocs && <ContentIndicator />}
</div>
<div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}>
Info
</div>
</div>
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
</StyledWrapper>

View File

@@ -3,7 +3,6 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
.editing-mode {
cursor: pointer;
color: ${(props) => props.theme.colors.text.yellow};
}
`;

View File

@@ -19,7 +19,7 @@ const EnvironmentSelector = ({ collection }) => {
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
{activeEnvironment ? activeEnvironment.name : 'No Environment'}
<p className="text-nowrap truncate max-w-32">{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
<IconCaretDown className="caret" size={14} strokeWidth={2} />
</div>
);
@@ -78,7 +78,10 @@ const EnvironmentSelector = ({ collection }) => {
<IconDatabaseOff size={18} strokeWidth={1.5} />
<span className="ml-2">No Environment</span>
</div>
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
<div className="dropdown-item border-top" onClick={() => {
handleSettingsIconClick();
dropdownTippyRef.current.hide();
}}>
<div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5} />
</div>

View File

@@ -10,6 +10,11 @@ import Modal from 'components/Modal';
const CreateEnvironment = ({ collection, onClose }) => {
const dispatch = useDispatch();
const inputRef = useRef();
const validateEnvironmentName = (name) => {
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
};
const formik = useFormik({
enableReinitialize: true,
initialValues: {
@@ -17,9 +22,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
.min(1, 'Must be at least 1 character')
.max(50, 'Must be 50 characters or less')
.required('Name is required')
.test('duplicate-name', 'Environment already exists', validateEnvironmentName)
}),
onSubmit: (values) => {
dispatch(addEnvironment(values.name, collection.uid))

View File

@@ -1,8 +1,9 @@
import React, { useRef, useEffect } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash, IconAlertCircle } from '@tabler/icons';
import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCheck } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { uuid } from 'utils/common';
@@ -13,7 +14,7 @@ import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions
import toast from 'react-hot-toast';
import { Tooltip } from 'react-tooltip';
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables, onClose }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const addButtonRef = useRef(null);
@@ -84,6 +85,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
formik.setFieldValue(formik.values.length, newVariable, false);
};
const onActivate = () => {
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
.then(() => {
if (environment) {
toast.success(`Environment changed to ${environment.name}`);
onClose();
} else {
toast.success(`No Environments are active now`);
}
})
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
};
const handleRemoveVar = (id) => {
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
};
@@ -183,13 +197,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
</div>
</div>
<div>
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
<div className="flex items-center">
<button type="submit" className="submit btn btn-sm btn-secondary mt-2 flex items-center" onClick={formik.handleSubmit}>
<IconDeviceFloppy size={16} strokeWidth={1.5} className="mr-1" />
Save
</button>
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
<button type="submit" className="ml-2 px-1 submit btn btn-sm btn-close mt-2 flex items-center" onClick={handleReset}>
<IconRefresh size={16} strokeWidth={1.5} className="mr-1" />
Reset
</button>
<button type="submit" className="submit btn btn-sm btn-close mt-2 flex items-center" onClick={onActivate}>
<IconCircleCheck size={16} strokeWidth={1.5} className="mr-1" />
Activate
</button>
</div>
</StyledWrapper>
);

View File

@@ -5,7 +5,7 @@ import DeleteEnvironment from '../../DeleteEnvironment';
import RenameEnvironment from '../../RenameEnvironment';
import EnvironmentVariables from './EnvironmentVariables';
const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
const EnvironmentDetails = ({ environment, collection, setIsModified, onClose }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [openCopyModal, setOpenCopyModal] = useState(false);
@@ -38,7 +38,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
</div>
<div>
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} />
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} onClose={onClose} />
</div>
</div>
);

View File

@@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
padding: 8px 10px;
border-left: solid 2px transparent;
text-decoration: none;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: none;

View File

@@ -8,8 +8,10 @@ import ImportEnvironment from '../ImportEnvironment';
import ManageSecrets from '../ManageSecrets';
import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
import ToolHint from 'components/ToolHint';
import { isEqual } from 'lodash';
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified }) => {
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified, onClose }) => {
const { environments } = collection;
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
@@ -23,6 +25,11 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
useEffect(() => {
if (selectedEnvironment) {
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
if (hasSelectedEnvironmentChanged) {
setSelectedEnvironment(_selectedEnvironment);
}
setOriginalEnvironmentVariables(selectedEnvironment.variables);
return;
}
@@ -103,13 +110,15 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
{environments &&
environments.length &&
environments.map((env) => (
<div
key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
>
<span className="break-all">{env.name}</span>
</div>
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
<div
id={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
>
<span className="break-all">{env.name}</span>
</div>
</ToolHint>
))}
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
+ <span>Create</span>
@@ -132,6 +141,7 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
collection={collection}
setIsModified={setIsModified}
originalEnvironmentVariables={originalEnvironmentVariables}
onClose={onClose}
/>
</div>
</StyledWrapper>

View File

@@ -72,6 +72,7 @@ const EnvironmentSettings = ({ collection, onClose }) => {
collection={collection}
isModified={isModified}
setIsModified={setIsModified}
onClose={onClose}
/>
</Modal>
);

View File

@@ -6,10 +6,9 @@ import { IconX } from '@tabler/icons';
import { isWindowsOS } from 'utils/common/platform';
import slash from 'utils/common/slash';
const FilePickerEditor = ({ value, onChange, collection }) => {
value = value || [];
const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false }) => {
const dispatch = useDispatch();
const filenames = value
const filenames = (isSingleFilePicker ? [value] : value || [])
.filter((v) => v != null && v != '')
.map((v) => {
const separator = isWindowsOS() ? '\\' : '/';
@@ -20,7 +19,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
const title = filenames.map((v) => `- ${v}`).join('\n');
const browse = () => {
dispatch(browseFiles())
dispatch(browseFiles([], [!isSingleFilePicker ? "multiSelections": ""]))
.then((filePaths) => {
// If file is in the collection's directory, then we use relative path
// Otherwise, we use the absolute path
@@ -34,7 +33,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
return filePath;
});
onChange(filePaths);
onChange(isSingleFilePicker ? filePaths[0] : filePaths);
})
.catch((error) => {
console.error(error);
@@ -42,14 +41,14 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
};
const clear = () => {
onChange([]);
onChange(isSingleFilePicker ? '' : []);
};
const renderButtonText = (filenames) => {
if (filenames.length == 1) {
return filenames[0];
}
return filenames.length + ' files selected';
return filenames.length + ' file(s) selected';
};
return filenames.length > 0 ? (
@@ -66,9 +65,9 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
</div>
) : (
<button className="btn btn-secondary px-1" style={{ width: '100%' }} onClick={browse}>
Select Files
{isSingleFilePicker ? 'Select File' : 'Select Files'}
</button>
);
};
export default FilePickerEditor;
export default FilePickerEditor;

View File

@@ -1,12 +1,9 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
table {
td {
&:first-child {
width: 120px;
}
}
.editing-mode {
cursor: pointer;
color: ${(props) => props.theme.colors.text.yellow};
}
`;

View File

@@ -0,0 +1,66 @@
import 'github-markdown-css/github-markdown.css';
import get from 'lodash/get';
import { updateFolderDocs } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
const Documentation = ({ collection, folder }) => {
const dispatch = useDispatch();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const [isEditing, setIsEditing] = useState(false);
const docs = get(folder, 'root.docs', '');
const toggleViewMode = () => {
setIsEditing((prev) => !prev);
};
const onEdit = (value) => {
dispatch(
updateFolderDocs({
folderUid: folder.uid,
collectionUid: collection.uid,
docs: value
})
);
};
const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
if (!folder) {
return null;
}
return (
<StyledWrapper className="mt-1 h-full w-full relative flex flex-col">
<div className="editing-mode flex justify-between items-center" role="tab" onClick={toggleViewMode}>
{isEditing ? 'Preview' : 'Edit'}
</div>
{isEditing ? (
<div className="mt-2 flex-1 max-h-[70vh]">
<CodeEditor
collection={collection}
theme={displayedTheme}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}
mode="application/text"
/>
<button type="submit" className="submit btn btn-sm btn-secondary my-6" onClick={onSave}>
Save
</button>
</div>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
)}
</StyledWrapper>
);
};
export default Documentation;

View File

@@ -7,6 +7,7 @@ import Script from './Script';
import Tests from './Tests';
import StyledWrapper from './StyledWrapper';
import Vars from './Vars';
import Documentation from './Documentation';
import DotIcon from 'components/Icons/Dot';
const ContentIndicator = () => {
@@ -60,6 +61,9 @@ const FolderSettings = ({ collection, folder }) => {
case 'vars': {
return <Vars collection={collection} folder={folder} />;
}
case 'docs': {
return <Documentation collection={collection} folder={folder} />;
}
}
};
@@ -89,6 +93,9 @@ const FolderSettings = ({ collection, folder }) => {
Vars
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs
</div>
</div>
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>
</div>

View File

@@ -23,7 +23,7 @@ const EnvironmentSelector = () => {
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
<IconWorld className="globe" size={16} strokeWidth={1.5} />
{
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
activeEnvironment ? <div className='text-nowrap truncate max-w-32'>{activeEnvironment?.name}</div> : null
}
</ToolHint>
</div>

View File

@@ -2,12 +2,19 @@ import React, { useEffect, useRef } from 'react';
import toast from 'react-hot-toast';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
const CreateEnvironment = ({ onClose }) => {
const globalEnvs = useSelector((state) => state?.globalEnvironments?.globalEnvironments);
const validateEnvironmentName = (name) => {
const trimmedName = name?.toLowerCase().trim();
return globalEnvs.every((env) => env?.name?.toLowerCase().trim() !== trimmedName);
};
const dispatch = useDispatch();
const inputRef = useRef();
const formik = useFormik({
@@ -17,9 +24,10 @@ const CreateEnvironment = ({ onClose }) => {
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
.min(1, 'Must be at least 1 character')
.max(50, 'Must be 50 characters or less')
.required('Name is required')
.test('duplicate-name', 'Global Environment already exists', validateEnvironmentName)
}),
onSubmit: (values) => {
dispatch(addGlobalEnvironment({ name: values.name }))

View File

@@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
padding: 8px 10px;
border-left: solid 2px transparent;
text-decoration: none;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
text-decoration: none;

View File

@@ -8,6 +8,7 @@ import ConfirmSwitchEnv from './ConfirmSwitchEnv';
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
import ImportEnvironment from '../ImportEnvironment';
import { isEqual } from 'lodash';
import ToolHint from 'components/ToolHint/index';
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
const [openCreateModal, setOpenCreateModal] = useState(false);
@@ -112,13 +113,15 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
{environments &&
environments.length &&
environments.map((env) => (
<div
key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
>
<span className="break-all">{env.name}</span>
</div>
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
<div
id={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle click
>
<span className="break-all">{env.name}</span>
</div>
</ToolHint>
))}
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
+ <span>Create</span>

View File

@@ -9,7 +9,6 @@ const StyledMarkdownBodyWrapper = styled.div`
box-sizing: border-box;
height: 100%;
margin: 0 auto;
padding-top: 0.5rem;
font-size: 0.875rem;
h1 {
@@ -55,7 +54,7 @@ const StyledMarkdownBodyWrapper = styled.div`
height: 1px;
padding: 0;
margin: 24px 0;
background-color: var(--color-border-default);
background-color: var(--color-sidebar-collection-item-active-indent-border);
border: 0;
}
@@ -80,12 +79,6 @@ const StyledMarkdownBodyWrapper = styled.div`
}
}
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
`;
export default StyledMarkdownBodyWrapper;

View File

@@ -62,7 +62,7 @@ const Modal = ({
confirmText,
cancelText,
handleCancel,
handleConfirm,
handleConfirm = () => {},
children,
confirmDisabled,
hideCancel,
@@ -92,7 +92,7 @@ const Modal = ({
};
useFocusTrap(modalRef);
const closeModal = (args) => {
setIsClosing(true);
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
@@ -103,7 +103,7 @@ const Modal = ({
return () => {
document.removeEventListener('keydown', handleKeydown);
};
}, [disableEscapeKey, document]);
}, [disableEscapeKey, document, handleConfirm]);
let classes = 'bruno-modal';
if (isClosing) {

View File

@@ -13,9 +13,14 @@ const StyledWrapper = styled.div`
line-height: 30px;
overflow: hidden;
.CodeMirror-scroll {
overflow: hidden !important;
${'' /* padding-bottom: 50px !important; */}
pre.CodeMirror-placeholder {
color: ${(props) => props.theme.text};
padding-left: 0;
opacity: 0.5;
}
.CodeMirror-scroll {
overflow: visible !important;
position: relative;
display: block;
margin: 0px;

View File

@@ -5,7 +5,7 @@ import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper';
let CodeMirror;
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');
@@ -30,6 +30,7 @@ class MultiLineEditor extends Component {
lineWrapping: false,
lineNumbers: false,
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
placeholder: this.props.placeholder,
mode: 'brunovariables',
brunoVarInfo: {
variables

View File

@@ -1,8 +1,6 @@
import { createPortal } from 'react-dom';
function Portal({ children, wrapperId }) {
wrapperId = wrapperId || 'bruno-app-body';
return createPortal(children, document.getElementById(wrapperId));
function Portal({ children }) {
return createPortal(children, document.body);
}
export default Portal;

View File

@@ -90,7 +90,10 @@ const General = ({ close }) => {
};
const addCaCertificate = (e) => {
formik.setFieldValue('customCaCertificate.filePath', e.target.files[0]?.path);
const filePath = window?.ipcRenderer?.getFilePath(e?.target?.files?.[0]);
if (filePath) {
formik.setFieldValue('customCaCertificate.filePath', filePath);
}
};
const deleteCaCertificate = () => {

View File

@@ -20,6 +20,7 @@ import React from 'react';
* endsWith : ends with
* between : between
* isEmpty : is empty
* isNotEmpty : is not empty
* isNull : is null
* isUndefined : is undefined
* isDefined : is defined
@@ -51,6 +52,7 @@ const AssertionOperator = ({ operator, onChange }) => {
'endsWith',
'between',
'isEmpty',
'isNotEmpty',
'isNull',
'isUndefined',
'isDefined',

View File

@@ -24,6 +24,7 @@ import { useTheme } from 'providers/Theme';
* endsWith : ends with
* between : between
* isEmpty : is empty
* isNotEmpty : is not empty
* isNull : is null
* isUndefined : is undefined
* isDefined : is defined
@@ -61,6 +62,7 @@ const parseAssertionOperator = (str = '') => {
'endsWith',
'between',
'isEmpty',
'isNotEmpty',
'isNull',
'isUndefined',
'isDefined',
@@ -75,6 +77,7 @@ const parseAssertionOperator = (str = '') => {
const unaryOperators = [
'isEmpty',
'isNotEmpty',
'isNull',
'isUndefined',
'isDefined',
@@ -87,7 +90,7 @@ const parseAssertionOperator = (str = '') => {
'isArray'
];
const [operator, ...rest] = str.trim().split(' ');
const [operator, ...rest] = str.split(' ');
const value = rest.join(' ');
if (unaryOperators.includes(operator)) {
@@ -113,6 +116,7 @@ const parseAssertionOperator = (str = '') => {
const isUnaryOperator = (operator) => {
const unaryOperators = [
'isEmpty',
'isNotEmpty',
'isNull',
'isUndefined',
'isDefined',
@@ -142,19 +146,8 @@ const AssertionRow = ({
const { operator, value } = parseAssertionOperator(assertion.value);
return (
<tr key={assertion.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={assertion.name}
className="mousetrap"
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
/>
</td>
<>
<td>
<AssertionOperator
operator={operator}
@@ -162,7 +155,7 @@ const AssertionRow = ({
handleAssertionChange(
{
target: {
value: `${op} ${value}`
value: isUnaryOperator(op) ? op : `${op} ${value}`
}
},
assertion,
@@ -178,7 +171,7 @@ const AssertionRow = ({
theme={storedTheme}
readOnly={true}
onSave={onSave}
onChange={(newValue) =>
onChange={(newValue) => {
handleAssertionChange(
{
target: {
@@ -188,6 +181,7 @@ const AssertionRow = ({
assertion,
'value'
)
}
}
onRun={handleRun}
collection={collection}
@@ -211,7 +205,7 @@ const AssertionRow = ({
</button>
</div>
</td>
</tr>
</>
);
};

View File

@@ -4,6 +4,7 @@ const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
@@ -15,24 +16,15 @@ const Wrapper = styled.div`
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
font-weight: 600;
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(4) {
width: 70px;
}
select {
select {
background-color: transparent;
}
}
}
.btn-add-assertion {
font-size: 0.8125rem;
@@ -42,7 +34,8 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
color: ${(props) => props.theme.table.input.color};
background: transparent;
&:focus {
outline: none !important;

View File

@@ -6,6 +6,9 @@ import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxS
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import AssertionRow from './AssertionRow';
import StyledWrapper from './StyledWrapper';
import Table from 'components/Table/index';
import ReorderTable from 'components/ReorderTable/index';
import { moveAssertion } from 'providers/ReduxStore/slices/collections/index';
const Assertions = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -57,21 +60,43 @@ const Assertions = ({ item, collection }) => {
);
};
const handleAssertionDrag = ({ updateReorderedItem }) => {
dispatch(
moveAssertion({
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Expr</td>
<td>Operator</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
<Table
headers={[
{ name: 'Expr', accessor: 'expr', width: '30%' },
{ name: 'Operator', accessor: 'operator', width: '120px' },
{ name: 'Value', accessor: 'value', width: '30%' },
{ name: '', accessor: '', width: '15%' }
]}
>
<ReorderTable updateReorderedItem={handleAssertionDrag}>
{assertions && assertions.length
? assertions.map((assertion) => {
return (
return (
<tr key={assertion.uid} data-uid={assertion.uid}>
<td className='flex relative'>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={assertion.name}
className="mousetrap"
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
/>
</td>
<AssertionRow
key={assertion.uid}
assertion={assertion}
@@ -82,11 +107,12 @@ const Assertions = ({ item, collection }) => {
onSave={onSave}
handleRun={handleRun}
/>
);
})
</tr>
);
})
: null}
</tbody>
</table>
</ReorderTable>
</Table>
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
+ Add Assertion
</button>

View File

@@ -70,6 +70,15 @@ const AuthMode = ({ item, collection }) => {
>
Digest Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef?.current?.hide();
onModeChange('ntlm');
}}
>
NTLM Auth
</div>
<div
className="dropdown-item"
onClick={() => {

View File

@@ -0,0 +1,17 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
max-width: 400px;
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -0,0 +1,110 @@
import React from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateAuth } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const NTLMAuth = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const ntlmAuth = item.draft ? get(item, 'draft.request.auth.ntlm', {}) : get(item, 'request.auth.ntlm', {});
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateAuth({
mode: 'ntlm',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: username,
password: ntlmAuth.password,
domain: ntlmAuth.domain
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateAuth({
mode: 'ntlm',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: ntlmAuth.username,
password: password,
domain: ntlmAuth.domain
}
})
);
};
const handleDomainChange = (domain) => {
dispatch(
updateAuth({
mode: 'ntlm',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: ntlmAuth.username,
password: ntlmAuth.password,
domain: domain
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={ntlmAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
onRun={handleRun}
collection={collection}
item={item}
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
onRun={handleRun}
collection={collection}
item={item}
isSecret={true}
/>
</div>
<label className="block font-medium mb-2">Domain</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={ntlmAuth.domain || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleDomainChange(val)}
onRun={handleRun}
collection={collection}
item={item}
/>
</div>
</StyledWrapper>
);
};
export default NTLMAuth;

View File

@@ -6,6 +6,8 @@ import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import DigestAuth from './DigestAuth';
import WsseAuth from './WsseAuth';
import NTLMAuth from './NTLMAuth';
import ApiKeyAuth from './ApiKeyAuth';
import StyledWrapper from './StyledWrapper';
import { humanizeRequestAuthMode } from 'utils/collections/index';
@@ -31,6 +33,9 @@ const Auth = ({ item, collection }) => {
case 'digest': {
return <DigestAuth collection={collection} item={item} />;
}
case 'ntlm': {
return <NTLMAuth collection={collection} item={item} />;
}
case 'oauth2': {
return <OAuth2 collection={collection} item={item} />;
}

View File

@@ -0,0 +1,65 @@
import styled from 'styled-components';
const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(2) {
width: 45%;
}
&:nth-child(3) {
width: 25%;
}
&:nth-child(4) {
width: 70px;
}
}
}
.btn-add-param {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
color: ${(props) => props.theme.table.input.color};
background: transparent;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='radio'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@@ -0,0 +1,164 @@
import React, { useState, useEffect } from 'react';
import { get, cloneDeep, isArray } from 'lodash';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addFile as _addFile, updateFile, deleteFile } from 'providers/ReduxStore/slices/collections/index';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import FilePickerEditor from 'components/FilePickerEditor/index';
import SingleLineEditor from 'components/SingleLineEditor/index';
const FileBody = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const params = item.draft ? get(item, 'draft.request.body.file') : get(item, 'request.body.file');
const [enabledFileUid, setEnableFileUid] = useState(params && params.length ? params[0].uid : '');
const addFile = () => {
dispatch(
_addFile({
itemUid: item.uid,
collectionUid: collection.uid,
})
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param);
switch (type) {
case 'filePath': {
param.filePath = e.target.filePath;
param.contentType = "";
break;
}
case 'contentType': {
param.contentType = e.target.contentType;
break;
}
case 'selected': {
param.selected = e.target.selected;
setEnableFileUid(param.uid)
break;
}
}
dispatch(
updateFile({
param: param,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const handleRemoveParams = (param) => {
dispatch(
deleteFile({
paramUid: param.uid,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>
<div className="flex items-center justify-center">File</div>
</td>
<td>
<div className="flex items-center justify-center">Content-Type</div>
</td>
<td>
<div className="flex items-center justify-center">Selected</div>
</td>
<td></td>
</tr>
</thead>
<tbody>
{params && params.length
? params.map((param, index) => {
return (
<tr key={param.uid}>
<td>
<FilePickerEditor
isSingleFilePicker={true}
value={param.filePath}
onChange={(path) =>
handleParamChange(
{
target: {
filePath: path
}
},
param,
'filePath'
)
}
collection={collection}
/>
</td>
<td>
<SingleLineEditor
className="flex items-center justify-center"
onSave={onSave}
theme={storedTheme}
placeholder="Auto"
value={param.contentType}
onChange={(newValue) =>
handleParamChange(
{
target: {
contentType: newValue
}
},
param,
'contentType'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center justify-center">
<input
key={param.uid}
type="radio"
name="selected"
checked={enabledFileUid === param.uid || param.selected}
tabIndex="-1"
className="mr-1 mousetrap"
onChange={(e) => handleParamChange(e, param, 'selected')}
/>
</div>
</td>
<td>
<div className="flex items-center justify-center">
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<div>
<button className="btn-add-param text-link pr-2 pt-3 select-none" onClick={addFile}>
+ Add File
</button>
</div>
</StyledWrapper>
);
};
export default FileBody;

View File

@@ -19,16 +19,8 @@ const Wrapper = styled.div`
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-param {
font-size: 0.8125rem;

View File

@@ -7,11 +7,14 @@ import { useTheme } from 'providers/Theme';
import {
addFormUrlEncodedParam,
updateFormUrlEncodedParam,
deleteFormUrlEncodedParam
deleteFormUrlEncodedParam,
moveFormUrlEncodedParam
} from 'providers/ReduxStore/slices/collections';
import MultiLineEditor from 'components/MultiLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import ReorderTable from 'components/ReorderTable/index';
import Table from 'components/Table/index';
const FormUrlEncodedParams = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -64,75 +67,84 @@ const FormUrlEncodedParams = ({ item, collection }) => {
);
};
const handleParamDrag = ({ updateReorderedItem }) => {
dispatch(
moveFormUrlEncodedParam({
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Key</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
<Table
headers={[
{ name: 'Key', accessor: 'key', width: '40%' },
{ name: 'Value', accessor: 'value', width: '46%' },
{ name: '', accessor: '', width: '14%' }
]}
>
<ReorderTable updateReorderedItem={handleParamDrag}>
{params && params.length
? params.map((param, index) => {
return (
<tr key={param.uid}>
<td>
return (
<tr key={param.uid} data-uid={param.uid}>
<td className='flex relative'>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'name')}
/>
</td>
<td>
<MultiLineEditor
value={param.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
allowNewlines={true}
onRun={handleRun}
collection={collection}
item={item}
/>
</td>
<td>
<div className="flex items-center">
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'name')}
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
</td>
<td>
<MultiLineEditor
value={param.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
allowNewlines={true}
onRun={handleRun}
collection={collection}
item={item}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
</ReorderTable>
</Table>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
+ Add Param
</button>

View File

@@ -154,7 +154,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
</div>
<GraphQLSchemaActions item={item} collection={collection} onSchemaLoad={setSchema} toggleDocs={toggleDocs} />
</div>
<section className="flex w-full mt-5 flex-1">{getTabPanel(focusedTab.requestPaneTab)}</section>
<section className="flex w-full mt-5 flex-1 relative">{getTabPanel(focusedTab.requestPaneTab)}</section>
</StyledWrapper>
);
};

View File

@@ -49,7 +49,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full relative">
<>
<button
className="btn-add-param text-link px-4 py-4 select-none absolute top-0 right-0 z-10"
onClick={onPrettify}
@@ -68,7 +68,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
</>
);
};

View File

@@ -19,15 +19,7 @@ const Wrapper = styled.div`
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-param {

View File

@@ -7,12 +7,15 @@ import { useTheme } from 'providers/Theme';
import {
addMultipartFormParam,
updateMultipartFormParam,
deleteMultipartFormParam
deleteMultipartFormParam,
moveMultipartFormParam
} from 'providers/ReduxStore/slices/collections';
import MultiLineEditor from 'components/MultiLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import FilePickerEditor from 'components/FilePickerEditor';
import Table from 'components/Table/index';
import ReorderTable from 'components/ReorderTable/index';
const MultipartFormParams = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -54,6 +57,10 @@ const MultipartFormParams = ({ item, collection }) => {
param.value = e.target.value;
break;
}
case 'contentType': {
param.contentType = e.target.value;
break;
}
case 'enabled': {
param.enabled = e.target.checked;
break;
@@ -78,93 +85,124 @@ const MultipartFormParams = ({ item, collection }) => {
);
};
const handleParamDrag = ({ updateReorderedItem }) => {
dispatch(
moveMultipartFormParam({
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Key</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
<Table
headers={[
{ name: 'Key', accessor: 'key', width: '29%' },
{ name: 'Value', accessor: 'value', width: '29%' },
{ name: 'Content-Type', accessor: 'content-type', width: '28%' },
{ name: '', accessor: '', width: '14%' }
]}
>
<ReorderTable updateReorderedItem={handleParamDrag}>
{params && params.length
? params.map((param, index) => {
return (
<tr key={param.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'name')}
return (
<tr key={param.uid} className='w-full' data-uid={param.uid}>
<td className="flex relative">
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'name')}
/>
</td>
<td>
{param.type === 'file' ? (
<FilePickerEditor
value={param.value}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
collection={collection}
/>
</td>
<td>
{param.type === 'file' ? (
<FilePickerEditor
value={param.value}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
collection={collection}
/>
) : (
<MultiLineEditor
onSave={onSave}
theme={storedTheme}
value={param.value}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
allowNewlines={true}
collection={collection}
item={item}
/>
)}
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
) : (
<MultiLineEditor
onSave={onSave}
theme={storedTheme}
value={param.value}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
allowNewlines={true}
collection={collection}
item={item}
/>
)}
</td>
<td>
<MultiLineEditor
onSave={onSave}
theme={storedTheme}
placeholder="Auto"
value={param.contentType}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'contentType'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
</ReorderTable>
</Table>
<div>
<button className="btn-add-param text-link pr-2 pt-3 mt-2 select-none" onClick={addParam}>
+ Add Param

View File

@@ -54,6 +54,14 @@ const StyledWrapper = styled.div`
.CodeMirror-search-hint {
display: inline;
}
.cm-s-default span.cm-property {
color: #1f61a0 !important;
}
.cm-s-default span.cm-variable {
color: #397d13 !important;
}
`;
export default StyledWrapper;

View File

@@ -19,7 +19,7 @@ import { IconWand } from '@tabler/icons';
import onHasCompletion from './onHasCompletion';
let CodeMirror;
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');

View File

@@ -103,7 +103,7 @@ const QueryParams = ({ item, collection }) => {
);
};
const handleParamDrag = ({ updateReorderedItem }) => {
const handleQueryParamDrag = ({ updateReorderedItem }) => {
dispatch(
moveQueryParam({
collectionUid: collection.uid,
@@ -117,7 +117,6 @@ const QueryParams = ({ item, collection }) => {
<StyledWrapper className="w-full flex flex-col absolute">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Query</div>
<Table
headers={[
{ name: 'Name', accessor: 'name', width: '31%' },
@@ -125,7 +124,7 @@ const QueryParams = ({ item, collection }) => {
{ name: '', accessor: '', width: '13%' }
]}
>
<ReorderTable updateReorderedItem={handleParamDrag}>
<ReorderTable updateReorderedItem={handleQueryParamDrag}>
{queryParams && queryParams.length
? queryParams.map((param, index) => (
<tr key={param.uid} data-uid={param.uid}>
@@ -153,7 +152,7 @@ const QueryParams = ({ item, collection }) => {
/>
</td>
<td>
<div className="flex items-center">
<div className="flex items-center justify-center">
<input
type="checkbox"
checked={param.enabled}
@@ -188,7 +187,7 @@ const QueryParams = ({ item, collection }) => {
</code>
</div>
`}
infotipId="path-param-InfoTip"
infotipId="path-param-InfoTip"
/>
</div>
<table>
@@ -241,11 +240,7 @@ const QueryParams = ({ item, collection }) => {
: null}
</tbody>
</table>
{!(pathParams && pathParams.length) ?
<div className="title pr-2 py-3 mt-2 text-xs">
</div>
: null}
{!(pathParams && pathParams.length) ? <div className="title pr-2 py-3 mt-2 text-xs"></div> : null}
</div>
</StyledWrapper>
);

View File

@@ -70,7 +70,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const handleGenerateCode = (e) => {
e.stopPropagation();
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
if (item?.request?.url !== '' || (item.draft?.request?.url !== undefined && item.draft?.request?.url !== '')) {
setGenerateCodeItemModalOpen(true);
} else {
toast.error('URL is required');

View File

@@ -128,6 +128,15 @@ const RequestBodyMode = ({ item, collection }) => {
SPARQL
</div>
<div className="label-item font-medium">Other</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('file');
}}
>
File / Binary
</div>
<div
className="dropdown-item"
onClick={() => {

View File

@@ -8,6 +8,7 @@ import { useTheme } from 'providers/Theme';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import FileBody from '../FileBody/index';
const RequestBody = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -48,6 +49,7 @@ const RequestBody = ({ item, collection }) => {
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
item={item}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
@@ -61,6 +63,10 @@ const RequestBody = ({ item, collection }) => {
);
}
if (bodyMode === 'file') {
return <FileBody item={item} collection={collection}/>
}
if (bodyMode === 'formUrlEncoded') {
return <FormUrlEncodedParams item={item} collection={collection} />;
}
@@ -71,4 +77,4 @@ const RequestBody = ({ item, collection }) => {
return <StyledWrapper className="w-full">No Body</StyledWrapper>;
};
export default RequestBody;
export default RequestBody;

View File

@@ -19,15 +19,7 @@ const Wrapper = styled.div`
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-header {

View File

@@ -4,12 +4,14 @@ import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
import { addRequestHeader, updateRequestHeader, deleteRequestHeader, moveRequestHeader } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
import Table from 'components/Table/index';
import ReorderTable from 'components/ReorderTable/index';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const RequestHeaders = ({ item, collection }) => {
@@ -63,22 +65,31 @@ const RequestHeaders = ({ item, collection }) => {
);
};
const handleHeaderDrag = ({ updateReorderedItem }) => {
dispatch(
moveRequestHeader({
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{headers && headers.length
<Table
headers={[
{ name: 'Key', accessor: 'key', width: '34%' },
{ name: 'Value', accessor: 'value', width: '46%' },
{ name: '', accessor: '', width: '20%' }
]}
>
<ReorderTable updateReorderedItem={handleHeaderDrag}>
{headers && headers.length
? headers.map((header) => {
return (
<tr key={header.uid}>
<td>
<tr key={header.uid} data-uid={header.uid}>
<td className='flex relative'>
<SingleLineEditor
value={header.name}
theme={storedTheme}
@@ -140,8 +151,8 @@ const RequestHeaders = ({ item, collection }) => {
);
})
: null}
</tbody>
</table>
</ReorderTable>
</Table>
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
+ Add Header
</button>

View File

@@ -1,10 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
}
`;
export default StyledWrapper;

View File

@@ -5,7 +5,6 @@ import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Tests = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -28,19 +27,17 @@ const Tests = ({ item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
value={tests || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
<CodeEditor
collection={collection}
value={tests || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
);
};

View File

@@ -19,16 +19,8 @@ const Wrapper = styled.div`
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-var {
font-size: 0.8125rem;
@@ -38,7 +30,8 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
color: ${(props) => props.theme.table.input.color};
background: transparent;
&:focus {
outline: none !important;

View File

@@ -3,13 +3,15 @@ import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
import { addVar, updateVar, deleteVar, moveVar } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
import Table from 'components/Table/index';
import ReorderTable from 'components/ReorderTable/index';
const VarsTable = ({ item, collection, vars, varType }) => {
const dispatch = useDispatch();
@@ -73,35 +75,41 @@ const VarsTable = ({ item, collection, vars, varType }) => {
);
};
const handleVarDrag = ({ updateReorderedItem }) => {
dispatch(
moveVar({
type: varType,
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
{varType === 'request' ? (
<td>
<div className="flex items-center">
<span>Value</span>
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
</div>
</td>
)}
<td></td>
</tr>
</thead>
<tbody>
{vars && vars.length
<Table
headers={[
{ name: 'Name', accessor: 'name', width: '40%' },
{ name: varType === 'request' ? (
<div className="flex items-center">
<span>Value</span>
</div>
) : (
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
</div>
), accessor: 'value', width: '46%' },
{ name: '', accessor: '', width: '14%' }
]}
>
<ReorderTable updateReorderedItem={handleVarDrag}>
{vars && vars.length
? vars.map((_var) => {
return (
<tr key={_var.uid}>
<td>
<tr key={_var.uid} data-uid={_var.uid}>
<td className='flex relative'>
<input
type="text"
autoComplete="off"
@@ -152,8 +160,8 @@ const VarsTable = ({ item, collection, vars, varType }) => {
);
})
: null}
</tbody>
</table>
</ReorderTable>
</Table>
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={handleAddVar}>
+ Add
</button>

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.card {
background: ${(props) => props.theme.requestTabPanel.card.bg};
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
div.hr {
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
height: 1px;
}
div.border-top {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,47 @@
import { IconLoader2, IconFile } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const RequestIsLoading = ({ item }) => {
return <StyledWrapper>
<div className='flex flex-col p-4'>
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
<div>
<div className='font-medium flex items-center gap-2 pb-4'>
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
File Info
</div>
<div className='hr'/>
<div className='flex items-center mt-2'>
<span className='w-12 mr-2 text-muted'>Name:</span>
<div>
{item?.name}
</div>
</div>
<div className='flex items-center mt-1'>
<span className='w-12 mr-2 text-muted'>Path:</span>
<div className='break-all'>
{item?.pathname}
</div>
</div>
<div className='flex items-center mt-1 pb-4'>
<span className='w-12 mr-2 text-muted'>Size:</span>
<div>
{item?.size?.toFixed?.(2)} MB
</div>
</div>
<div className='hr'/>
<div className='flex items-center gap-2 mt-4'>
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
<span>Loading...</span>
</div>
</div>
</div>
</div>
</StyledWrapper>
}
export default RequestIsLoading;

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.card {
background: ${(props) => props.theme.requestTabPanel.card.bg};
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
div.hr {
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
height: 1px;
}
div.border-top {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,89 @@
import { IconLoader2, IconFile } from '@tabler/icons';
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
const RequestNotLoaded = ({ collection, item }) => {
const dispatch = useDispatch();
const handleLoadRequestViaWorker = () => {
!item?.loading && dispatch(loadRequestViaWorker({ collectionUid: collection?.uid, pathname: item?.pathname }));
}
const handleLoadRequest = () => {
!item?.loading && dispatch(loadRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
}
return <StyledWrapper>
<div className='flex flex-col p-4'>
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
<div>
<div className='font-medium flex items-center gap-2 pb-4'>
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
File Info
</div>
<div className='hr'/>
<div className='flex items-center mt-2'>
<span className='w-12 mr-2 text-muted'>Name:</span>
<div>{item?.name}</div>
</div>
<div className='flex items-center mt-1'>
<span className='w-12 mr-2 text-muted'>Path:</span>
<div className='break-all'>{item?.pathname}</div>
</div>
<div className='flex items-center mt-1 pb-4'>
<span className='w-12 mr-2 text-muted'>Size:</span>
<div>{item?.size?.toFixed?.(2)} MB</div>
</div>
{!item?.error && (
<>
<div className='hr'/>
<div className='text-muted text-xs mt-4 mb-2'>
Due to its large size, this request wasn't loaded automatically.
</div>
<div className='flex flex-col gap-6 mt-4'>
<div className='flex flex-col'>
<button
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
onClick={handleLoadRequest}
>
Load Request
</button>
<small className='text-muted mt-1'>
May cause the app to freeze temporarily while it runs.
</small>
</div>
<div className='flex flex-col'>
<button
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
onClick={handleLoadRequestViaWorker}
>
Load Request in Background
</button>
<small className='text-muted mt-1'>
Runs in background.
</small>
</div>
</div>
</>
)}
{item?.loading && (
<>
<div className='hr mt-4'/>
<div className='flex items-center gap-2 mt-4'>
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
<span>Loading...</span>
</div>
</>
)}
</div>
</div>
</div>
</StyledWrapper>
}
export default RequestNotLoaded;

View File

@@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
align-items: center;
justify-content: center;
width: 10px;
min-width: 10px;
padding: 0;
cursor: col-resize;
background: transparent;

View File

@@ -20,8 +20,11 @@ import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
import SecuritySettings from 'components/SecuritySettings';
import FolderSettings from 'components/FolderSettings';
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
import { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index';
import { produce } from 'immer';
import CollectionOverview from 'components/CollectionSettings/Overview';
import RequestNotLoaded from './RequestNotLoaded';
import RequestIsLoading from './RequestIsLoading';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350;
@@ -39,13 +42,18 @@ const RequestTabPanel = () => {
const _collections = useSelector((state) => state.collections.collections);
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
let collections = produce(_collections, draft => {
let collections = produce(_collections, (draft) => {
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
if (collection) {
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
globalEnvironments,
activeGlobalEnvironmentUid
});
const globalEnvSecrets = getGlobalEnvironmentVariablesMasked({ globalEnvironments, activeGlobalEnvironmentUid });
collection.globalEnvironmentVariables = globalEnvironmentVariables;
collection.globalEnvSecrets = globalEnvSecrets;
}
});
@@ -148,6 +156,11 @@ const RequestTabPanel = () => {
if (focusedTab.type === 'collection-settings') {
return <CollectionSettings collection={collection} />;
}
if (focusedTab.type === 'collection-overview') {
return <CollectionOverview collection={collection} />;
}
if (focusedTab.type === 'folder-settings') {
const folder = findItemInCollection(collection, focusedTab.folderUid);
return <FolderSettings collection={collection} folder={folder} />;
@@ -162,6 +175,14 @@ const RequestTabPanel = () => {
return <RequestNotFound itemUid={activeTabUid} />;
}
if (item?.partial) {
return <RequestNotLoaded item={item} collection={collection} />
}
if (item?.loading) {
return <RequestIsLoading item={item} />
}
const handleRun = async () => {
dispatch(sendRequest(item, collection.uid)).catch((err) =>
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {

View File

@@ -2,10 +2,18 @@ import React from 'react';
import CloseTabIcon from './CloseTabIcon';
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick }) => {
const getTabInfo = (type, tabName) => {
switch (type) {
case 'collection-settings': {
return (
<div onDoubleClick={handleDoubleClick} className="flex items-center flex-nowrap overflow-hidden">
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
<span className="ml-1 leading-6">Collection</span>
</div>
);
}
case 'collection-overview': {
return (
<>
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
@@ -23,7 +31,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
}
case 'folder-settings': {
return (
<div className="flex items-center flex-nowrap overflow-hidden">
<div onDoubleClick={handleDoubleClick} className="flex items-center flex-nowrap overflow-hidden">
<IconFolder size={18} strokeWidth={1.5} className="text-yellow-600 min-w-[18px]" />
<span className="ml-1 leading-6 truncate">{tabName || 'Folder'}</span>
</div>

View File

@@ -1,6 +1,6 @@
import React, { useState, useRef, Fragment } from 'react';
import get from 'lodash/get';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { closeTabs, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
@@ -70,16 +70,16 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
};
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
return (
<StyledWrapper
className="flex items-center justify-between tab-container px-1"
className={`flex items-center justify-between tab-container px-1 ${tab.preview ? "italic" : ""}`}
onMouseUp={handleMouseUp} // Add middle-click behavior here
>
{tab.type === 'folder-settings' ? (
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} tabName={folder?.name} />
<SpecialTab handleCloseClick={handleCloseClick} handleDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} tabName={folder?.name} />
) : (
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
<SpecialTab handleCloseClick={handleCloseClick} handleDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))} type={tab.type} />
)}
</StyledWrapper>
);
@@ -144,8 +144,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
/>
)}
<div
className="flex items-baseline tab-label pl-2"
className={`flex items-baseline tab-label pl-2 ${tab.preview ? "italic" : ""}`}
onContextMenu={handleRightClick}
onDoubleClick={() => dispatch(makeTabPermanent({ uid: tab.uid }))}
onMouseUp={(e) => {
if (!item.draft) return handleMouseUp(e);

View File

@@ -1,14 +1,41 @@
import React, { useState, useEffect } from 'react';
import CodeEditor from 'components/CodeEditor/index';
import { get } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import { Document, Page } from 'react-pdf';
import { useState } from 'react';
import 'pdfjs-dist/build/pdf.worker';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import 'react-pdf/dist/esm/Page/TextLayer.css';
import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';
import ReactPlayer from 'react-player';
const VideoPreview = React.memo(({ contentType, dataBuffer }) => {
const [videoUrl, setVideoUrl] = useState(null);
useEffect(() => {
const videoType = contentType.split(';')[0];
const byteArray = Buffer.from(dataBuffer, 'base64');
const blob = new Blob([byteArray], { type: videoType });
const url = URL.createObjectURL(blob);
setVideoUrl(url);
return () => URL.revokeObjectURL(url);
}, [contentType, dataBuffer]);
if (!videoUrl) return <div>Loading video...</div>;
return (
<ReactPlayer
url={videoUrl}
controls
muted={true}
width="100%"
height="100%"
onError={(e) => console.error('Error loading video:', e)}
/>
);
});
const QueryResultPreview = ({
previewTab,
@@ -73,9 +100,7 @@ const QueryResultPreview = ({
);
}
case 'preview-video': {
return (
<video controls src={`data:${contentType.replace(/\;(.*)/, '')};base64,${dataBuffer}`} className="mx-auto" />
);
return <VideoPreview contentType={contentType} dataBuffer={dataBuffer} />;
}
default:
case 'raw': {

View File

@@ -20,14 +20,14 @@ const formatResponse = (data, mode, filter) => {
}
if (data === null) {
return data;
return 'null';
}
if (mode.includes('json')) {
let isValidJSON = false;
try {
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object';
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object'
} catch (error) {
console.log('Error parsing JSON: ', error.message);
}

View File

@@ -11,7 +11,7 @@ const ResponseSave = ({ item }) => {
const saveResponseToFile = () => {
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:save-response-to-file', response, item.requestSent.url)
.invoke('renderer:save-response-to-file', response, item?.requestSent?.url)
.then(resolve)
.catch((err) => {
toast.error(get(err, 'error.message') || 'Something went wrong!');

View File

@@ -43,7 +43,7 @@ const Timeline = ({ request, response }) => {
<div className="mt-4">
<pre className="line response font-bold">
<span className="arrow">{'<'}</span> {response.status} {response.statusText}
<span className="arrow">{'<'}</span> {response.status} - {response.statusText}
</pre>
{responseHeaders.map((h) => {

View File

@@ -9,6 +9,7 @@ import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun }
import slash from 'utils/common/slash';
import ResponsePane from './ResponsePane';
import StyledWrapper from './StyledWrapper';
import { areItemsLoading } from 'utils/collections';
const getRelativePath = (fullPath, pathname) => {
// convert to unix style path
@@ -59,7 +60,7 @@ export default function RunnerResults({ collection }) {
pathname: info.pathname,
relativePath: getRelativePath(collection.pathname, info.pathname)
};
if (newItem.status !== 'error') {
if (newItem.status !== 'error' && newItem.status !== 'skipped') {
if (newItem.testResults) {
const failed = newItem.testResults.filter((result) => result.status === 'fail');
newItem.testStatus = failed.length ? 'fail' : 'pass';
@@ -106,6 +107,8 @@ export default function RunnerResults({ collection }) {
return (item.status !== 'error' && item.testStatus === 'fail') || item.assertionStatus === 'fail';
});
let isCollectionLoading = areItemsLoading(collection);
if (!items || !items.length) {
return (
<StyledWrapper className="px-4 pb-4">
@@ -116,7 +119,7 @@ export default function RunnerResults({ collection }) {
<div className="mt-6">
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
</div>
{isCollectionLoading ? <div className='my-1 danger'>Requests in this collection are still loading.</div> : null}
<div className="mt-6">
<label>Delay (in ms)</label>
<input
@@ -163,29 +166,35 @@ export default function RunnerResults({ collection }) {
<div className="pb-2 font-medium test-summary">
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
</div>
{runnerInfo?.statusText ?
<div className="pb-2 font-medium danger">
{runnerInfo?.statusText}
</div>
: null}
{items.map((item) => {
return (
<div key={item.uid}>
<div className="item-path mt-2">
<div className="flex items-center">
<span>
{item.status !== 'error' && item.testStatus === 'pass' ? (
{item.status !== 'error' && item.testStatus === 'pass' && item.status !== 'skipped' ? (
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
) : (
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
)}
</span>
<span
className={`mr-1 ml-2 ${item.status == 'error' || item.testStatus == 'fail' ? 'danger' : ''}`}
className={`mr-1 ml-2 ${item.status == 'error' || item.status == 'skipped' || item.testStatus == 'fail' ? 'danger' : ''}`}
>
{item.relativePath}
</span>
{item.status !== 'error' && item.status !== 'completed' ? (
{item.status !== 'error' && item.status !== 'skipped' && item.status !== 'completed' ? (
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
) : item.responseReceived?.status ? (
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
(<span className="mr-1">{item.responseReceived?.status}</span>
<span>{item.responseReceived?.statusText}</span>)
<span className="mr-1">{item.responseReceived?.status}</span>
-&nbsp;
<span>{item.responseReceived?.statusText}</span>
</span>
) : (
<span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}>

View File

@@ -0,0 +1,12 @@
import styled from 'styled-components';
const Wrapper = styled.div`
.partial {
color: ${(props) => props.theme.colors.text.yellow};
}
.error {
color: ${(props) => props.theme.colors.text.danger};
}
`;
export default Wrapper;

View File

@@ -0,0 +1,21 @@
import RequestMethod from "../RequestMethod";
import { IconLoader2, IconAlertTriangle, IconAlertCircle } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper";
const CollectionItemIcon = ({ item }) => {
if (item?.error) {
return <StyledWrapper><IconAlertCircle className="w-fit mr-2 error" size={18} strokeWidth={1.5} /></StyledWrapper>;
}
if (item?.loading) {
return <IconLoader2 className="animate-spin w-fit mr-2" size={18} strokeWidth={1.5} />;
}
if (item?.partial) {
return <StyledWrapper><IconAlertTriangle size={18} className="w-fit mr-2 partial" strokeWidth={1.5} /></StyledWrapper>;
}
return <RequestMethod item={item} />;
};
export default CollectionItemIcon;

View File

@@ -12,10 +12,15 @@ const DeleteCollectionItem = ({ onClose, item, collection }) => {
const isFolder = isItemAFolder(item);
const onConfirm = () => {
dispatch(deleteItem(item.uid, collection.uid)).then(() => {
if (isFolder) {
// close all tabs that belong to the folder
// including the folder itself and its children
const tabUids = [...recursivelyGetAllItemUids(item.items), item.uid]
dispatch(
closeTabs({
tabUids: recursivelyGetAllItemUids(item.items)
tabUids: tabUids
})
);
} else {

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