Compare commits

...

571 Commits

Author SHA1 Message Date
Anoop M D
e8f88d8a13 Merge pull request #832 from martinsefcik/bug/680
fix (#680): fixed non-string variables to be displayed correctly in code editor
2023-11-01 14:56:46 +05:30
Anoop M D
c1f5d474b1 Merge pull request #818 from DaPutzy/main
fix links and images in translated readme's
2023-11-01 14:55:33 +05:30
Anoop M D
c04b83bcaa Merge pull request #809 from mheidinger/fix-gql-schema-interpolation
fix graphql schema interpolation
2023-11-01 14:54:16 +05:30
Anoop M D
f8b5a48076 Merge pull request #831 from BandhiyaHardik/main
fix: In README.md in Stay in touch section the Twitter changed to it's new name 𝕏
2023-11-01 14:51:49 +05:30
Anoop M D
6fd84fcec3 Merge pull request #833 from n00o/bugfix/Adjust_Divider_Offset
fix (#402): response/body cursor offset on resize
2023-11-01 14:51:09 +05:30
Anoop M D
6687a20835 Merge pull request #834 from psqaure11/new
capitalize git to Git for clarity
2023-11-01 14:49:13 +05:30
Anoop M D
68a508e26a Merge pull request #844 from panda7789/bugfix/multipart-form
fix: Multipart Form
2023-11-01 14:48:35 +05:30
Anoop M D
05bf40fa82 chore: bumped version to 1.0.1 2023-11-01 14:38:33 +05:30
Anoop M D
49f29fa2af fix(#836): Fix graphql blank screen issue 2023-11-01 14:36:28 +05:30
Linhart Lukáš
89e0d3ae7e Fix Multipart Form 2023-11-01 07:05:05 +01:00
unknown
2d4f337f03 capitalize git to Git for clarity 2023-10-31 09:49:38 +05:30
n00o
fb4ad848d6 Update index.js
Pass prettier test.
2023-10-30 20:59:40 -04:00
n00o
31ecc281a8 Fix response/body cursor offset on resize
Now response/body will be refresh each time it gets resized so the cursor always stays in the right place.
2023-10-30 20:10:36 -04:00
Martin Sefcik
745cb85f95 fix (#680): fixed defined non-string variables to be displayed correctly in code editor 2023-10-30 23:43:44 +01:00
HardikBandhiya
aac4a33c92 Update readme.md
changed Twitter's old name to new 𝕏
2023-10-31 01:59:43 +05:30
Anoop M D
32d9c4833e chore: bump version to 1.0.0 2023-10-30 23:33:29 +05:30
DaPutzy
65f688dbc2 chrore: fix logo's in translated readme's 2023-10-29 13:49:44 +00:00
DaPutzy
3f6f4ccd12 chore: fix links and images in translated readme's 2023-10-29 13:04:11 +00:00
Anoop M D
fa8ec4267f Merge pull request #807 from vaugenwake/feature/import-graphql-collection
feat: Adding ability to import postman graphql collections
2023-10-29 11:40:39 +05:30
Vaugen Wakeling
99b25fc5b2 feat: Remove unneeded formatting 2023-10-27 17:21:16 +01:00
Max Heidinger
a0fe80604e fix: fix graphql schema interpolation 2023-10-27 14:59:09 +02:00
Anoop M D
7ac39bcf5c fix(#777): fixing snap issue 2023-10-27 16:18:49 +05:30
Vaugen Wakeling
5df9981e20 feat: Adding ability to import postman graphql collections
Fixes: #790
2023-10-27 00:34:16 +01:00
Anoop M D
03184fa1e8 Merge pull request #803 from Makar8000/postman-import-auth
feature/Auth support for postman collection imports
2023-10-27 03:15:12 +05:30
Makar
e4b2b651f5 Auth support for postman collection imports 2023-10-26 13:23:43 -05:00
Anoop M D
fd6b083ae5 chore: fixed broken links in documentation 2023-10-26 22:55:00 +05:30
Anoop M D
91d9e81da4 Merge pull request #788 from areebniyas/feature/restructure-folders
Restructure and merge documentation files
2023-10-26 22:27:56 +05:30
Anoop M D
1e435b8e10 Merge pull request #791 from Its-treason/feature/variable-name-validation
Enforce variable strictness & validation
2023-10-26 22:19:57 +05:30
Anoop M D
834014b5d1 Merge pull request #797 from mheidinger/local-gql-schema
feat: add loading local graphql schema file
2023-10-26 22:15:34 +05:30
Anoop M D
85b22b53aa Merge pull request #795 from DaPutzy/feature/additional-script-context-roots
[feature] allow multiple script context roots
2023-10-26 22:06:23 +05:30
Anoop M D
24f92d2b4e Merge pull request #794 from survivant/feature/add-comment-by-line
Fix the issue when only the first line was imported
2023-10-26 10:09:27 +05:30
Anoop M D
5f5ad288dc Merge pull request #796 from usebruno/revert-779-feature/close-tabs-with-mouse
Revert "Make Tabs closable by mouseclick. Closes #778"
2023-10-26 09:54:55 +05:30
Anoop M D
e274aedb9a Revert "Make Tabs closable by mouseclick. Closes #778" 2023-10-26 09:54:32 +05:30
DaPutzy
7fa68731d2 feat: allow multiple script context roots 2023-10-25 23:16:28 +00:00
Sebastien Dionne
c6440ab849 Fix import error 2023-10-25 17:54:04 -04:00
Its-treason
d52163ceb3 test: Fix failing test 'should parse the json file' 2023-10-25 23:30:31 +02:00
Its-treason
827df18c62 feat(#682): Add validation for request vars & dynamic vars 2023-10-25 23:13:37 +02:00
areebniyas
3c9e913349 merge development and contributing doc 2023-10-26 00:48:05 +05:30
Anoop M D
7f7b3f479a feat: nudge users to view docs on managing secrets 2023-10-26 00:38:44 +05:30
Its-treason
3a5a213242 Merge branch 'main' into feature/variable-name-validation 2023-10-25 21:00:18 +02:00
Anoop M D
1f0bde4a12 feat: nudge users how to disable ssl cert vaidation 2023-10-26 00:12:38 +05:30
Anoop M D
2e38300a67 chore: updated package lock file 2023-10-25 23:55:07 +05:30
Anoop M D
b731aa28be Merge pull request #763 from grubersjoe/feat(#762)/preferences
Add menu entry for preferences and add shortcut for it
2023-10-25 23:47:50 +05:30
Anoop M D
ddfd3bdf15 Merge pull request #757 from survivant/feature/cleanup-docs-typo
Fix some typo in documentation
2023-10-25 23:31:26 +05:30
Anoop M D
32b696da57 chore: pr #766 ux polish 2023-10-25 23:29:59 +05:30
Anoop M D
36f19ec7bc Merge pull request #766 from martinsefcik/feature/save-response-to-file
Added possibility to save response to file
2023-10-25 23:12:38 +05:30
Anoop M D
53e85f4568 chore: added styles to "ipcRenderer not found" error notification 2023-10-25 23:08:05 +05:30
Anoop M D
4607814c09 Merge pull request #768 from Its-treason/feat/dev-hints
feat(#740): Add hints for local bruno setup
2023-10-25 22:57:50 +05:30
Anoop M D
c40f8e654c Merge pull request #771 from n00o/feature/Tree-View
Add better CodeMirror folding options
2023-10-25 22:52:19 +05:30
Anoop M D
7fbd8f0a8a Merge pull request #779 from frixxx/feature/close-tabs-with-mouse
Make Tabs closable by mouseclick. Closes #778
2023-10-25 22:40:33 +05:30
Anoop M D
c5a47ba7ec Merge pull request #774 from jwetzell/main
fix cert/key loading in electron for relative path
2023-10-25 22:38:23 +05:30
Anoop M D
13a7e4538e Merge pull request #782 from arnaduga/feat/frenchDocTranslation
Feat/french doc translation
2023-10-25 22:35:10 +05:30
Arnaud
36443edca3 feat: fixes after review 2023-10-25 18:57:34 +02:00
Arnaud
5e94ea0e83 feat: fixes after review 2023-10-25 18:56:28 +02:00
Arnaud
fbeaa96419 feat: fixes after review 2023-10-25 18:51:51 +02:00
Arnaud
8e5234d76a feat: fixes after review 2023-10-25 18:50:06 +02:00
Arnaud
f4b9b53922 feat: update readme to get French link 2023-10-25 18:41:45 +02:00
Arnaud
3f1b4dc628 feat: add doc French translation 2023-10-25 18:38:50 +02:00
Matthias Vöpel
7cd46c3a6c Make Tabs closable by mouseclick.
Make Tabs closable by mouseclick.
2023-10-25 10:59:53 +02:00
Joel Wetzell
3300e67900 fix cert/key loading in electron for relative path 2023-10-24 18:24:52 -05:00
Sebastien Dionne
abd7edf1db Update publishing.md
Co-authored-by: mj-h <martinjhoecker@gmail.com>
2023-10-24 16:31:19 -04:00
Its-treason
7b1223d2c5 feat(#740): Add hints for local bruno setup
- Custom error when opening bruno in the browser
- Custom error when starting electron without the web server running
- Force Next dev server to use port 3000
2023-10-24 18:59:35 +02:00
n00o
a3e6ecddbb Add better CodeMirror folding options
Add XML fold to Response and Body. Add count of object directly inside of fold.  Include hotkeys Ctrl-Y to fold and Ctrl-I to unfold all XML/JSON data.
2023-10-24 11:21:47 -04:00
Martin Sefcik
276c9ce1b0 added possibility to save response to file 2023-10-24 17:08:43 +02:00
Jonathan Gruber
3687594cc4 feat(#762): Add menu entry for preferences and add shortcut fora it 2023-10-24 13:17:47 +02:00
Anoop M D
5274d77660 Merge pull request #758 from aviskase/main
[bugfix] fix OpenAPI method filtering predicate
2023-10-24 09:14:32 +05:30
Yuliya Bagriy
ba3db06ac6 fix prettier error 2023-10-24 09:16:30 +06:00
Yuliya Bagriy
2f9762da0e fix OpenAPI method filtering predicate 2023-10-24 08:02:21 +06:00
Sebastien Dionne
766d103e20 Fix some typo in documentation 2023-10-23 21:24:42 -04:00
Anoop M D
8f398bacb2 Merge pull request #748 from x0vr/feature/de-translations-for-docs
Add german translation to documentation files
2023-10-24 05:09:15 +05:30
Anoop M D
e0afbdbb74 chore: release v0.27.2 2023-10-24 03:53:31 +05:30
Anoop M D
d90cb91a19 Merge pull request #754 from survivant/feature/add-yaml-support-insomnia
[feature] Can import Insomnia Yaml file
2023-10-24 03:51:46 +05:30
Anoop M D
1026addc8f Merge pull request #745 from aviskase/main
OpenAPI import - filter out non-methods on path item objects parsing
2023-10-24 03:50:53 +05:30
Anoop M D
ecf849b207 Merge pull request #753 from survivant/feature/add-comment-by-line
Postman import : Each line will start by a comment
2023-10-24 03:49:51 +05:30
Anoop M D
98a87dcc00 Merge pull request #752 from jwetzell/main
fix: update @usebruno/js to a working version in bruno-electron
2023-10-24 03:48:33 +05:30
Sebastien Dionne
973f8e036d Can import Insomnia Yaml file 2023-10-23 17:32:04 -04:00
Sebastien Dionne
da9afcabe8 Each line will start by a comment 2023-10-23 17:21:52 -04:00
Joel Wetzell
177f23e19f update @usebruno/js version 2023-10-23 15:59:20 -05:00
x0v3rR
97b634f608 add further improvements 2023-10-23 21:43:12 +02:00
x0v3rR
c572ba574f improve translation 2023-10-23 21:31:48 +02:00
x0v3rR
84c8676804 fix some translations 2023-10-23 20:43:36 +02:00
Anoop M D
586e26fa2e Merge pull request #746 from flof/bugfix/misleading-collection-remove-message
fix misleading message
2023-10-24 00:08:14 +05:30
x0v3rR
83779dd2e1 add german translation 2023-10-23 20:33:50 +02:00
Yuliya Bagriy
e77a9adebd handle non-methods on OpenAPI import 2023-10-24 00:20:30 +06:00
Florian Fankhauser
e8156c5611 fix misleading message 2023-10-23 20:19:05 +02:00
Anoop M D
471e1780a0 bumped version to v0.27.1 2023-10-23 23:14:41 +05:30
Anoop M D
89c5dae881 Merge pull request #742 from bplatta/updates/import-openapi-v3
OpenApi v3 case fixes
2023-10-23 23:11:55 +05:30
bplatta
6c52263c3c Update openapi v3 import handle no operationId and no security 2023-10-23 11:35:07 -05:00
Anoop M D
76787b4080 Merge pull request #730 from survivant/feature/openapi-add-yaml-support
Feature/openapi add yaml support
2023-10-23 17:10:09 +05:30
Anoop M D
34ea7f0673 Merge pull request #733 from survivant/bugfix/import-postman-scripts
[bugfix] Put scripts in comments in Postman import
2023-10-23 17:09:36 +05:30
Sebastien Dionne
73062472b7 Add scripts in comments 2023-10-23 07:07:25 -04:00
Sebastien Dionne
eb043fb465 Regenerate package-lock.json 2023-10-23 06:57:26 -04:00
Sebastien Dionne
a46e8851c6 Merge branch 'main' into feature/openapi-add-yaml-support
# Conflicts:
#	package-lock.json
2023-10-23 06:56:38 -04:00
Sebastien Dionne
4d23b4b372 Regenerate package-lock.json 2023-10-23 06:52:23 -04:00
Max Heidinger
45126c99ab feat: add loading local graphql schema file 2023-10-23 11:22:21 +02:00
Anoop M D
5ba3903c31 fix(#701): fix bru cli issue 2023-10-23 14:09:24 +05:30
Sebastien Dionne
07dd409acd English message 2023-10-22 18:41:45 -04:00
Sebastien Dionne
ddc2740b31 Merge remote-tracking branch 'origin/feature/openapi-add-yaml-support' into feature/openapi-add-yaml-support 2023-10-22 18:28:47 -04:00
Sebastien Dionne
d06d28b7fc Cam import Yaml OpenApi file 2023-10-22 18:28:28 -04:00
Anoop M D
b35596bb59 chore: updated monorepo package versions 2023-10-23 03:28:22 +05:30
Anoop M D
a35b04ad48 chore: updated trademark details in readme 2023-10-23 02:58:07 +05:30
Anoop M D
9d9e4b5065 chore: bumped version to v0.26.0 2023-10-23 01:57:00 +05:30
Anoop M D
6b07cc6e8f chore: added publishing docs 2023-10-23 01:56:05 +05:30
Anoop M D
296e067222 chore(#684): renamed use to enabled in collection proxy config 2023-10-23 01:37:30 +05:30
Anoop M D
49ea7f33e6 Merge pull request #578 from bplatta/feature/import-openapi-v3
feat: openapi v3 import
2023-10-23 01:25:02 +05:30
Its-treason
7267ba6485 feat(#682): Enforce environment variable strictness 2023-10-22 21:54:17 +02:00
Anoop M D
59c5c24752 Merge pull request #664 from cardonator/bugfix/#661
adjust styles on environment dropdown and modal
2023-10-23 01:18:20 +05:30
Anoop M D
64b46caacd Merge pull request #705 from cwilper/feature/gql-schema-prerequest-postresponse
feat: run prerequest/postresponse actions when fetching gql schema
2023-10-23 01:15:35 +05:30
Anoop M D
213f21bd01 Merge pull request #670 from jcari-dev/fix/contributing-typo
docs(contributing): Typo Correction
2023-10-23 00:31:22 +05:30
Anoop M D
861604075a Merge pull request #683 from SoulKa/fix/571-url-encoded-with-colon
fix #571 - allow newlines and encode uri form data
2023-10-23 00:25:17 +05:30
Anoop M D
d2de19f31c Merge pull request #689 from DogukanUrker/main
Turkish translation for readme.md and contributing.md files.
2023-10-23 00:21:30 +05:30
Anoop M D
ffa4708ea8 Merge pull request #694 from janmonschke/bugfix/overflowing-code-editor
fix: overflowing code editor
2023-10-23 00:19:53 +05:30
Anoop M D
6b85323d1f Merge pull request #700 from martinsefcik/bug/699
bug(#699): fixed crashing app on clicking on request result in runner
2023-10-23 00:14:45 +05:30
Anoop M D
f22e975d90 Merge pull request #725 from cwilper/fix/https-proxy-agent-opts
fix: respect rejectUnauthorized and ca opts when proxying https
2023-10-23 00:01:55 +05:30
Chris Wilper
1d58bdab59 fix: respect rejectUnauthorized and ca opts when proxying https 2023-10-22 11:29:49 -04:00
Anoop M D
564825868e Merge pull request #666 from wheinze/docs-linebreaks
docs(readme): add missing line breaks
2023-10-22 16:51:43 +05:30
Anoop M D
e8199f102a Merge pull request #697 from aliasgar55/main
fixed: #686
2023-10-22 16:50:44 +05:30
Anoop M D
82c6e83d56 Merge pull request #711 from survivant/feature/pre-req-and-test
Added pre-request and tests scripts support from Postman Collection
2023-10-22 16:23:19 +05:30
Anoop M D
6bfdd58b59 Merge pull request #720 from aliasgar55/bugfix/save-pref
fix: #692
2023-10-22 16:20:55 +05:30
Ali Asgar
9fd937bfbc fix: #692 2023-10-22 02:49:10 +05:30
Chris Wilper
bbb904437f chore: re-sync proxy-util.js copies 2023-10-21 14:32:41 -04:00
Sebastien Dionne
fe59cd3223 Added pre-request and tests scripts support from Postman Collection 2023-10-20 19:02:04 -04:00
Chris Wilper
a6ddf12ee1 feat: run postrequest code after gql schema fetch 2023-10-20 12:13:35 -04:00
Chris Wilper
7e8073cbdb chore: consolidate postresponse-related code 2023-10-20 12:08:04 -04:00
Chris Wilper
846ac66ca3 feat: run prerequest code before gql schema fetch 2023-10-20 10:57:26 -04:00
Chris Wilper
c2bdddaa59 chore: consolidate prerequest-related code 2023-10-20 10:42:53 -04:00
Martin Sefcik
55c52971d6 bug(#699): fixed crashing app on clicking on request result in runner 2023-10-20 14:34:04 +02:00
Ali Asgar
3ed7dd3893 fixed: #686 2023-10-20 15:59:36 +05:30
Jan Monschke
16cf654e57 fix: overflowing code editor 2023-10-20 10:59:00 +02:00
Doğukan Ürker
89fbb42512 Turkish translation for contributing.md. 2023-10-20 00:23:13 +03:00
Doğukan Ürker
b00d35f007 Turkish translation for ReadMe page. 2023-10-20 00:22:19 +03:00
Louis Wilke
ddddab8112 571 - allow and encode multiline uri form data 2023-10-19 21:29:17 +02:00
Anoop M D
f8f38802a9 chore: added gql docs build command in snap build 2023-10-20 00:17:14 +05:30
Anoop M D
fc25ea597c chore: bumped version to 0.26.0 2023-10-20 00:15:41 +05:30
Anoop M D
72df4419ff fix(#679): fixed issue while removing client certs 2023-10-20 00:13:10 +05:30
Anoop M D
e9a8763ca0 fix: fix proxy setting issue 2023-10-20 00:07:25 +05:30
Anoop M D
a1d54eacf6 Merge pull request #672 from therealrinku/fix
feat: scroll to the active tab on click
2023-10-19 23:49:02 +05:30
Anoop M D
f75121d426 Merge pull request #681 from Its-treason/bugfix/snap-build
fix(#581): Fix snap package build
2023-10-19 23:41:54 +05:30
Anoop M D
16137a8189 fix: fixed issue in cmd+r inside query result viewer 2023-10-19 23:39:45 +05:30
Anoop M D
922c1ff253 Merge pull request #622 from Its-treason/feature/better-image-preview
feat(#593): Show real response in image preview
2023-10-19 23:27:22 +05:30
Its-treason
d3e625ef84 fix(#581): Fix snap package build 2023-10-19 19:52:21 +02:00
Its-treason
623ac0bac6 Merge branch 'main' into feature/better-image-preview 2023-10-19 19:43:31 +02:00
therealrinku
7dc09faa9b feat: scroll to the active tab on click 2023-10-19 09:39:23 +05:45
jcari-dev
21c49093a5 fixed typo in contributing.md 2023-10-18 18:00:45 -04:00
Waldemar Heinze
f2c32a8812 docs(readme): add missing linebreaks 2023-10-18 21:35:43 +02:00
cardonator
232027ff4e adjust styles on environment dropdown and modal 2023-10-18 12:42:39 -06:00
Anoop M D
f08871d491 chore: pr #465 polish 2023-10-18 23:36:44 +05:30
Anoop M D
31c4d7ecfd Merge pull request #465 from Scotsoo/bugfix/fixes-251
Bugfix/fixes 251
2023-10-18 23:34:47 +05:30
Anoop M D
0dcd50f546 Merge branch 'main' of github.com:usebruno/bruno 2023-10-18 23:28:06 +05:30
Anoop M D
ece5ad3175 Merge pull request #536 from jarne/bugfix/bug-451-max-window-reset
Fix: save window maximized state
2023-10-18 23:27:55 +05:30
Anoop M D
1f6366f262 fix: allow empty values to be stored as timeout 2023-10-18 23:13:50 +05:30
Anoop M D
3f7ace8bdf Merge pull request #598 from Ross-Gargett/bugfix/negative-timeout
Fixes Bug #597 : Timeout field validation
2023-10-18 22:56:58 +05:30
Anoop M D
0ca035f492 Merge branch 'main' into bugfix/negative-timeout 2023-10-18 22:56:29 +05:30
Anoop M D
79be625fb5 Merge pull request #599 from game5413/fix/collection-dropdown
fix: event mouse up triggered when hold mouse click at anywhere and release at sidebar
2023-10-18 22:53:15 +05:30
Anoop M D
2d29eaa31a Merge pull request #540 from Its-treason/fix/ctrl-w-closes-bruno-unexpectedly
fix(#529): Fix Ctrl+W closes Bruno
2023-10-18 21:21:06 +05:30
Anoop M D
2c9b1b801a Merge pull request #656 from mheidinger/gql-schema-clientcert-proxy
feat: use client certificates and proxy for fetching graphql schema
2023-10-18 21:07:52 +05:30
Max Heidinger
82abda85fe feat: use client certificates and proxy for fetching graphql schema 2023-10-18 15:51:19 +02:00
Anoop M D
2ce5a6eacb Merge pull request #532 from j0k3r/fix/update-github-workflows
Update GitHub workflows
2023-10-18 11:56:46 +05:30
Anoop M D
7484a8a72b refactor: post response lifecyle execution 2023-10-18 11:53:10 +05:30
Anoop M D
5f77ee342b Merge pull request #587 from rayoz12/552-run-post-response-vars-and-scripts-on-error
Fixes: #552: Runs Post response vars and scripts on HTTP error.
2023-10-18 11:15:49 +05:30
Anoop M D
e715a47eb6 Merge pull request #644 from premeaswaran/main
Fixes #637: autofill folder name as collection name
2023-10-18 11:13:12 +05:30
Anoop M D
ae36bdfe7b chore: removed redundant code 2023-10-18 11:09:25 +05:30
Anoop M D
1aa91cd2be chore: updated pr template 2023-10-18 10:57:54 +05:30
Anoop M D
95419413b5 fix: fixed collection proxy settings tooltip 2023-10-18 10:55:13 +05:30
Anoop M D
b6da0b2113 chore: pr #632 polish 2023-10-18 10:54:51 +05:30
Anoop M D
d5a72b1a5d Merge pull request #632 from KaniZ0r/bugfix/vars-in-assert
Adds variable interpolate to assert runtime
2023-10-18 10:48:49 +05:30
Anoop M D
ca8b2a58d4 Merge pull request #647 from Sl-Alex/feature/exact-size-in-responsesize
Fix #646: show exact response size in bytes on mouse hover
2023-10-18 10:46:53 +05:30
Anoop M D
9bd7723c48 Merge pull request #652 from mvmn/main
Ukrainian translation of the documentation
2023-10-18 10:44:17 +05:30
Anoop M D
d809a58deb chore: pr polish (#596) 2023-10-18 10:25:01 +05:30
Mykola Makhin
edbe9ee4f0 Merge pull request #2 from mvmn/feature/ua-translations-for-docs
Corrected link to contributing page from UA readme
2023-10-18 02:26:43 +03:00
Mykola Makhin
924f415176 Corrected link to contributing page from UA readme 2023-10-18 02:25:35 +03:00
Mykola Makhin
c3decadf30 Merge pull request #1 from mvmn/feature/ua-translations-for-docs
Feature/ua translations for docs
2023-10-18 02:24:06 +03:00
Mykola Makhin
b150694a5c Ukrainian translation for the development doc 2023-10-18 02:21:10 +03:00
Its-treason
b3ee0af226 feat: Use real content size, fix runner, use the correct content charset 2023-10-17 23:03:10 +02:00
Its-treason
187e482feb feat(#596): Show real response in image preview 2023-10-17 23:02:09 +02:00
Oleksii Slabchenko
d034d599c5 Fix #646: show exact response size in bytes on mouse hover 2023-10-17 20:45:58 +02:00
Anoop M D
d767a144f2 Merge pull request #596 from mirkogolze/feature/proxy-global-and-collection
proxy settings on global and collection level
2023-10-17 23:34:45 +05:30
Prem Kumar Easwaran
ad8e9c2561 Autofill folder name as collection name 2023-10-17 21:07:32 +05:30
Jarne
0cf4f09608 Save window bounds only if not maximized 2023-10-17 17:25:20 +02:00
Anoop M D
b05d8d7da0 Merge pull request #639 from Joschasa/feature/add-collection-name-to-remove-dialog
feat: add collection name to remove dialog
2023-10-17 19:35:17 +05:30
André Glüpker
5f94fa1174 feat: add collection name to remove dialog 2023-10-17 15:37:51 +02:00
Anoop M D
b31d7044a0 Merge pull request #633 from dozed/fix/set-sparql-body
Fix: Set body on SPARQL request
2023-10-17 15:50:50 +05:30
Anoop M D
2acf8cae86 Merge pull request #630 from panda7789/bugfix/js-build
fix electron js build
2023-10-17 15:49:53 +05:30
Stefan Ollinger
4ab4f09987 Set body on SPARQL request 2023-10-17 11:23:11 +02:00
Mykola Makhin
7e654a81bf Ukrainian translation for ReadMe page 2023-10-17 12:06:45 +03:00
Linhart Lukáš
8c101a8684 Fix _next replacement 2023-10-17 10:46:00 +02:00
Linhart Lukáš
95190fa424 Print log from inner npm run 2023-10-17 10:45:40 +02:00
Heikki Pölönen
544edfa7d7 Adds variable interpolate to assert runtime 2023-10-17 11:42:40 +03:00
Linhart Lukáš
ba9766fbf0 fix js build 2023-10-17 08:41:56 +02:00
Mirko Golze
0cd4a4ed94 Merge branch 'main' into feature/proxy-global-and-collection
# Conflicts:
#	packages/bruno-electron/src/ipc/network/index.js
2023-10-17 08:30:44 +02:00
Ross Gargett
67218f5bb4 feedback: use formik and Yup for preferences form 2023-10-16 21:08:22 -07:00
Anoop M D
b28f7625e4 chore: update body selector styles 2023-10-17 06:52:52 +05:30
Anoop M D
5b00f7a8b3 Merge pull request #604 from donus3/feature/json-body-prettify
Feature (#550): JSON body request prettify
2023-10-17 06:49:49 +05:30
Anoop M D
fe1e260e92 Merge pull request #617 from panda7789/bugfix/client-cert-collections
fix collection client certs
2023-10-17 06:14:24 +05:30
Anoop M D
ee3ef0b68c Merge pull request #602 from premeaswaran/main
[User Experience] Change import icon
2023-10-17 06:07:53 +05:30
Linhart Lukáš
63e0df1640 fix collection client certs 2023-10-16 14:11:01 +02:00
Mirko Golze
1b9ad34f3f merge main into proxy branch 2023-10-16 12:12:09 +02:00
Mirko Golze
19463cd0cf merge main into proxy branch 2023-10-16 12:08:10 +02:00
Mirko Golze
3fa87fe99a merge main into proxy branch 2023-10-16 11:37:35 +02:00
Mirko Golze
658a47e03e Merge branch 'main' into feature/proxy-global-and-collection
# Conflicts:
#	packages/bruno-app/src/components/Preferences/General/index.js
#	packages/bruno-electron/src/ipc/network/index.js
#	packages/bruno-electron/src/store/preferences.js
2023-10-16 11:24:16 +02:00
Donus(ADA)
6e7fc2a9aa feat: add json body prettify 2023-10-16 14:19:06 +07:00
Prem Kumar Easwaran
1244716b9b Change import icon 2023-10-16 11:19:55 +05:30
Ross Gargett
ddd479ed45 fix: prevent non-numerical or negative timeouts 2023-10-15 19:38:47 -07:00
Anoop M D
a9459bc236 chore: bumped version to v0.25.0 2023-10-16 03:43:13 +05:30
Anoop M D
cdfa839cf3 chore: polish request timeout 2023-10-16 03:41:47 +05:30
Anoop M D
4c8ebe7765 Merge pull request #590 from snippetkid/557-configurable-request-timeout
Configurable request timeout
2023-10-16 03:18:20 +05:30
Anoop M D
15f3276663 Merge pull request #591 from oohsai/main
Create PULL_REQUEST_TEMPLATE.md
2023-10-16 03:15:28 +05:30
Anoop M D
1f8c4431e0 feat(#589): polish importing postman env 2023-10-16 03:13:06 +05:30
Anoop M D
6c92ad22ef Merge branch 'main' of github.com:usebruno/bruno 2023-10-16 02:20:49 +05:30
Anoop M D
4dd17247d1 Merge pull request #589 from premeaswaran/main
Feature: Support Import postman environment - Fixes issue #193
2023-10-16 02:20:26 +05:30
Anoop M D
58fbe2e64b chore: cleanup 2023-10-16 02:10:18 +05:30
Anoop M D
333564f687 feat(#275): polish client certificate support 2023-10-16 02:07:15 +05:30
Anoop M D
faa7cdb1d5 Merge pull request #275 from phoval/feat/mTlsInitialSupport
feat: client certificate support
2023-10-16 01:15:41 +05:30
Dipin Jagadish
3302e2e910 Merge branch 'main' into 557-configurable-request-timeout 2023-10-15 20:30:01 +01:00
Dipin Jagadish
0e1869139b fix: fixing merge conflicts 2023-10-15 20:19:08 +01:00
Anoop M D
353be75d9c fix: fixed bug in loading preferences 2023-10-16 00:45:54 +05:30
Mirko Golze
43c5b4cf53 proxy settings global and collection level 2023-10-15 20:30:13 +02:00
Mirko Golze
6dd6879e8f proxy settings global and collection level 2023-10-15 20:26:36 +02:00
Dipin Jagadish
955fcb795d Merge branch 'main' into 557-configurable-request-timeout 2023-10-15 18:34:41 +01:00
Dipin Jagadish
ad3f1b2331 feat: use timeout for axios requests 2023-10-15 18:26:17 +01:00
Mirko Golze
f2bdf2eaf6 Merge branch 'main' into feature/proxy-global-and-collection
# Conflicts:
#	packages/bruno-app/src/components/Preferences/General/index.js
#	packages/bruno-app/src/components/Preferences/index.js
#	packages/bruno-app/src/providers/App/useIpcEvents.js
#	packages/bruno-app/src/providers/Preferences/index.js
#	packages/bruno-electron/src/index.js
#	packages/bruno-electron/src/ipc/collection.js
#	packages/bruno-electron/src/store/preferences.js
2023-10-15 17:06:39 +02:00
Mirko Golze
470e9d0442 proxy settings 2023-10-15 16:40:50 +02:00
Anoop M D
4230bf8e41 chore: purge pageComponents 2023-10-15 16:03:13 +05:30
Anoop M D
7bf049a1f0 feat: use electron-store for saving preferences 2023-10-15 15:59:57 +05:30
Jack Scotson
074cbf0c03 cleanup 2023-10-15 10:25:35 +01:00
Jack Scotson
211f941b9f rm renameitem 2023-10-15 10:16:41 +01:00
Anoop M D
363cbe75d2 Merge pull request #433 from qweme32/feature/code-font
Added a setting that allows you to change the font of the code
2023-10-15 12:04:30 +05:30
Mykola Makhin
ead1225bc4 Ukrainian translation for contributing.md 2023-10-15 01:54:52 +03:00
Sai
61b705112e Create PULL_REQUEST_TEMPLATE.md 2023-10-15 00:52:33 +05:30
Jack Scotson
0f1fb72e21 fix(#251): Re-add platform var 2023-10-14 16:38:12 +01:00
Jack Scotson
7808a6db05 Revert "fix(#251, #265): phantoms folders fix on rename/delete needs to be run only on windows"
This reverts commit fcc12fb089.

# Conflicts:
#	packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
#	packages/bruno-app/src/utils/common/platform.js
2023-10-14 16:36:10 +01:00
Jack Scotson
b07ccbdfab adds PATH_SEPARATOR constant 2023-10-14 16:32:29 +01:00
Dipin Jagadish
a2b6bc5970 feat: add textbox for request timeout 2023-10-14 15:50:01 +01:00
Easwaran Prem K
3cc2e3d4fa Feature: Support Import postman environment - Fixes issue #193 2023-10-14 17:20:31 +05:30
game5413
a0be0e10ac fix event triggered when hold mouse click and release at sidebar 2023-10-14 18:10:49 +07:00
rayoz12
78eec9ea5c Fixes: #552
Runs Post response vars and scripts on error.

This normalises behaviour between:
- Assertions
- Scripts
- Tests
2023-10-14 21:12:37 +11:00
mirkogolze
97a300fbfc Merge branch 'usebruno:main' into feature/proxy-global-and-collection 2023-10-14 10:26:09 +02:00
Anoop M D
e898ebdfa9 Merge pull request #478 from survivant/feature/build-on-different-os
Add build support for different OS
2023-10-14 02:43:35 +05:30
nyyu
d6628d960e feat: support client certificates 2023-10-13 22:30:45 +02:00
Sebastien Dionne
e0266f0a69 Push package-lock.json 2023-10-13 16:25:52 -04:00
Sebastien Dionne
ec89acde48 Merge branch 'main' into feature/build-on-different-os 2023-10-13 16:18:57 -04:00
Anoop M D
102f7a5ecb Merge branch 'main' of github.com:usebruno/bruno 2023-10-14 01:01:31 +05:30
Anoop M D
19498363e1 feat(#583): collection level docs 2023-10-14 01:01:11 +05:30
Anoop M D
be857d7812 Merge pull request #570 from Nikhil569/fix538
Fixes colliding method and request name in sidebar
2023-10-14 00:30:18 +05:30
Anoop M D
e95d9fe611 Merge pull request #575 from panda7789/bugfix/switching-tabs-resets-timer
fix: switching tabs resets timer
2023-10-14 00:27:59 +05:30
Anoop M D
e7f130f8a1 Merge pull request #573 from therealrinku/fix
assert operator dropdown bgcolor fix
2023-10-14 00:12:21 +05:30
Anoop M D
d71e69ac3c chore: muted color for collection search close icon 2023-10-14 00:10:16 +05:30
Anoop M D
442e68283d chore: distrowise build scripts 2023-10-13 23:59:30 +05:30
Anoop M D
e218b8276d Merge pull request #576 from dw-0/feature/clear-search
feat: add button to clear collection search
2023-10-13 23:57:29 +05:30
bplatta
36ee1f542a build entire json object for request and only support first requestBody content type 2023-10-13 11:30:27 -05:00
dw-0
97871c25cf feat: add button to clear collection search
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2023-10-13 17:53:39 +02:00
bplatta
35db296c1f Update console logs 2023-10-13 10:50:47 -05:00
Linhart Lukáš
e90fc466b4 Dont reset timer when switch tabs 2023-10-13 17:27:17 +02:00
therealrinku
b11113b529 assert operator dropdown bgcolor fix 2023-10-13 21:05:13 +05:45
Nikhil569
79072785e2 Update index.js 2023-10-13 19:05:13 +05:30
Nikhil569
ce59170752 Update StyledWrapper.js 2023-10-13 19:00:57 +05:30
Nikhil569
60d3d07459 Update index.js 2023-10-13 19:00:26 +05:30
Nikhil569
4eb74c8ed3 Merge branch 'usebruno:main' into fix538 2023-10-13 18:56:23 +05:30
mirkogolze
9e4ff465af Merge branch 'usebruno:main' into feature/proxy-global-and-collection 2023-10-13 11:53:47 +02:00
bplatta
7b55d52c11 firstpass at adding openapi v3 import json 2023-10-12 21:53:14 -05:00
Anoop M D
6b668aaebe chore: bumped version to v0.24.0 2023-10-13 07:39:03 +05:30
Anoop M D
ad02ab8f36 Merge pull request #522 from foneazmi/main
fix(ui): add minHeight & minWidth
2023-10-13 07:17:34 +05:30
Anoop M D
5d1be2f18a chore: dispatch null response when req is cancelled 2023-10-13 07:13:18 +05:30
Anoop M D
6c3dc7bbd2 Merge pull request #471 from therealrinku/two
showing error response in response tab instead of alert message
2023-10-13 07:10:18 +05:30
Anoop M D
30adea3146 chore: graceful error handling 2023-10-13 07:06:40 +05:30
Anoop M D
eed952d43f Merge pull request #466 from therealrinku/one
feat: added active query, headers, vars and asserts count
2023-10-13 07:04:07 +05:30
Anoop M D
09f67abad6 Merge branch 'main' into one 2023-10-13 07:03:55 +05:30
Anoop M D
d595032f46 fix: postman collection export 2023-10-13 07:01:59 +05:30
Anoop M D
671fcecb38 Merge pull request #544 from kapami/feature/export-to-postman-collection
Feature/export to postman collection
2023-10-13 06:49:59 +05:30
Anoop M D
ca615e8662 Merge branch 'main' of github.com:usebruno/bruno 2023-10-13 06:46:37 +05:30
Anoop M D
d3ab2f28c6 feat: docs refactor 2023-10-13 06:46:14 +05:30
Anoop M D
a3cdc871d2 Merge pull request #549 from bacebu4/main
fix(bruno-cli): add missing variable to `prepareRequest`
2023-10-13 05:39:46 +05:30
Anoop M D
774ce327a9 Merge pull request #527 from martinsefcik/feature/507
Added support for debug level for console logging in scripts
2023-10-13 05:29:50 +05:30
Anoop M D
625a19e86c revert: #463 - style and right pane updates 2023-10-13 04:25:22 +05:30
Anoop M D
1087cacdb0 Merge pull request #463 from donus3/feature/docs
Bruno Docs
2023-10-13 04:07:28 +05:30
Anoop M D
faccca3921 Merge pull request #546 from bmrodgers148/main
AWS SigV4 Auth support
2023-10-13 04:01:08 +05:30
Nikhil569
3810152c6b Merge branch 'usebruno:main' into fix538 2023-10-12 20:49:02 +05:30
Anoop M D
816496246b Merge pull request #541 from brunohpaiva/feature/rpm-build
Add RPM linux build target
2023-10-12 14:12:32 +05:30
bacebu4
fcbecfb480 fix(bruno-cli): add missing variable to prepareRequest 2023-10-12 09:57:33 +02:00
Bruno Henrique Paiva
dc425b067b Merge branch 'main' into feature/rpm-build 2023-10-12 02:41:58 -03:00
mirkogolze
03011a41a8 Merge branch 'usebruno:main' into feature/proxy-global-and-collection 2023-10-12 07:40:46 +02:00
Anoop M D
46e59eac4b fix: snap publish command 2023-10-12 10:12:30 +05:30
Anoop M D
cf96527ffb fix: snap login command 2023-10-12 10:08:17 +05:30
Anoop M D
e4df7e3111 fix: snap login command 2023-10-12 09:59:24 +05:30
Anoop M D
3ca32d4199 fix: snap login command 2023-10-12 09:56:20 +05:30
Anoop M D
1298df384d fix: updated electron build command 2023-10-12 09:51:35 +05:30
Anoop M D
fbfdfdca04 fix: updated snap publish command 2023-10-12 09:49:33 +05:30
Anoop M D
36bf2c8648 feat: snapcraft release support 2023-10-12 09:45:23 +05:30
Brian Rodgers
ba8bfc6d17 Added AWS SigV4 to collection auth 2023-10-11 17:00:47 -07:00
Brian Rodgers
f781bd7d8a Merge remote-tracking branch 'upstream/main' 2023-10-11 15:54:56 -07:00
Miroslav Kapa
6e246410d5 remove of garbage 2023-10-12 00:38:05 +02:00
Miroslav Kapa
d4e07d2028 Merge branch 'main' of https://github.com/sthagen/usebruno-bruno into feature/export-to-postman-collection 2023-10-12 00:31:23 +02:00
Miroslav Kapa
542735e220 added file saving + added export of collection var 2023-10-12 00:30:58 +02:00
Bruno Henrique Paiva
062adf019c chore: add dist folder to .gitignore 2023-10-11 17:58:10 -03:00
Bruno Henrique Paiva
7a864e0054 build: add linux RPM target 2023-10-11 17:56:28 -03:00
Its-treason
72521a6007 fix(#529): Fix Ctrl+W closes Bruno
The default shortcut to close in the menu is Ctrl+W, I changed it
to Ctrl+Shift+Q because firefox uses this shortcut for closing
2023-10-11 22:09:11 +02:00
Nikhil569
0466cd6233 Shorten method name in sidebar 2023-10-12 00:49:49 +05:30
Jarne
8a0f7c6b8f Save window maximized state 2023-10-11 19:22:50 +02:00
Miroslav Kapa
d145275172 Merge branch 'main' of https://github.com/sthagen/usebruno-bruno into feature/export-to-postman-collection 2023-10-11 18:21:30 +02:00
Miroslav Kapa
6cbdbad09d UI addition for Postman export,partial generator 2023-10-11 18:20:36 +02:00
Jeremy Benoist
6d7f397d7b Update GitHub workflows
- jump to `actions/checkout` v4 (latest version)
- retrieve node version from NVM instead of hard-coded
- add a new job to run prettier (in case people skip pre-commit hook)
2023-10-11 13:00:16 +02:00
therealrinku
5ea9799c49 update: active vars count added 2023-10-11 15:34:09 +05:45
Mirko Golze
608d606f64 Merge branch 'main' into feature/proxy-global-and-collection
# Conflicts:
#	packages/bruno-app/src/components/ResponsePane/Placeholder/index.js
2023-10-11 08:30:51 +02:00
Donus(ADA)
d922376d57 feat(documentation): move function to the request pane 2023-10-11 09:27:08 +07:00
Donus(ADA)
33cd30315a refactor: move textarea styled-component to the DocumentEditor component file 2023-10-11 09:21:12 +07:00
Donus(ADA)
01c298c58e feat: support documentation 2023-10-11 09:21:12 +07:00
Donus(ADA)
250227a134 feat: more request and response pane ui responsive 2023-10-11 09:21:12 +07:00
Farkhan Azmi
21c9fda04f fix(ui): add minHeight & minWidth 2023-10-11 07:55:46 +07:00
Anoop M D
36caa5c2d3 revert pr: #476 2023-10-11 04:07:23 +05:30
Anoop M D
9beb422674 chore: release v0.23.0 2023-10-11 04:01:51 +05:30
Anoop M D
93f89698fd chore: json safeStringify 2023-10-11 03:57:19 +05:30
Anoop M D
cf8eb7e4ac Merge pull request #495 from Sl-Alex/fix-start-endwith-assertions
Fix #437 (start/endWith assertions)
2023-10-11 03:40:48 +05:30
Anoop M D
8d4e5bc732 Merge pull request #506 from martinsefcik/bug/505
fix(#505): fixed displaying shortcuts based on OS
2023-10-11 03:39:18 +05:30
Anoop M D
de223acb19 Merge pull request #520 from snippetkid/497-show-response-size
Show response size even if content-length header isn't present
2023-10-11 03:38:13 +05:30
Anoop M D
037793c113 Merge pull request #504 from grubersjoe/gql-collection-auth
feat(#354): Use collection level auth if available for introspection request
2023-10-11 03:36:39 +05:30
Anoop M D
5682c47d00 Merge pull request #519 from Nikhil569/main
feat: shows image if url return a image
2023-10-11 03:33:08 +05:30
Dipin Jagadish
32629cdcca fix: add response size in response pane 2023-10-10 22:56:38 +01:00
Anoop M D
1979be6c4b Merge pull request #518 from dozed/fix/error-handling
Fixed error handling
2023-10-11 03:08:49 +05:30
Anoop M D
84ed2aff72 Merge pull request #516 from dozed/feature/sparql-body
Add support for SPARQL bodies
2023-10-11 03:07:57 +05:30
Anoop M D
f602306069 Merge pull request #515 from Its-treason/fix/parse-query-params-on-request-creation
fix(#500): Parse query url on request creation
2023-10-11 03:00:38 +05:30
Nikhil569
9e8488d417 Merge branch 'main' of https://github.com/Nikhil569/bruno into main 2023-10-11 02:58:05 +05:30
Nikhil569
4b297f32e0 Would allow bruno to show images 2023-10-11 02:56:01 +05:30
Anoop M D
9c3c28be42 Merge pull request #512 from therealrinku/three
fix: add request and folder modal validation fixes
2023-10-11 02:26:58 +05:30
Stefan Ollinger
bd5bc4acb2 Fixed error handling 2023-10-10 21:51:32 +02:00
Stefan Ollinger
e20dc985ef Set request data 2023-10-10 21:43:43 +02:00
Stefan Ollinger
79379b23e2 Put SPARQL mode below TEXT 2023-10-10 20:52:13 +02:00
Stefan Ollinger
41abdbb7fa Support SPARQL bodies 2023-10-10 20:47:02 +02:00
Its-treason
d637334fd7 fix(#500): Parse query url on request creation 2023-10-10 19:19:19 +02:00
Jack Scotson
9ca22cd1d1 Merge tag 'v0.22.1' into bugfix/fixes-251
v0.22.1
2023-10-10 17:32:38 +01:00
Jack Scotson
ff3c666f48 use isWindowsOS() 2023-10-10 17:32:17 +01:00
therealrinku
0a2835a543 fix: add request and folder modal validation fixes 2023-10-10 19:21:21 +05:45
Martin Sefcik
f9f3b3ebc0 feat(#507): added support for debug level for console logging in scripts 2023-10-10 12:24:28 +02:00
Martin Sefcik
dfefb0a314 fix(#505): fixed displaying shortcuts based on OS 2023-10-10 11:49:52 +02:00
Jonathan Gruber
12f8ae37a6 feat(#354): Use collection level auth if available for introspection request 2023-10-10 11:26:10 +02:00
Oleksii Slabchenko
d5c24d417d Update package-lock.json 2023-10-10 11:01:57 +02:00
Mirko Golze
d64f4d3740 make code runnable 2023-10-10 07:49:22 +02:00
therealrinku
571b4459ac update: showing error in queryresult component 2023-10-10 10:51:45 +05:45
Rinku Chaudhari
d4683ab961 Merge branch 'main' into two 2023-10-10 10:00:07 +05:45
Oleksii Slabchenko
91b1814c55 Fix #437 (start/endWith assertions) 2023-10-10 01:31:05 +02:00
Anoop M D
ff3321d643 chore: bumped version to 0.22.1 2023-10-10 04:40:03 +05:30
Anoop M D
659a71491d publish: bru lang v0.7.0 2023-10-10 04:38:56 +05:30
Anoop M D
3d366477d0 feat(#309): use dotenv for .env file parsing 2023-10-10 04:22:21 +05:30
Anoop M D
beaa20c134 fix(#491): fix response overlay scroller 2023-10-10 01:08:00 +05:30
Mirko Golze
99fe4432c4 Merge branch 'main' into feature/proxy-global-and-collection 2023-10-09 21:11:20 +02:00
Mirko Golze
71e8ea457c try add ca file to global root caas 2023-10-09 21:09:52 +02:00
Anoop M D
53e49ffdd3 Merge pull request #476 from Levminer/main
Replace macOS icon
2023-10-10 00:11:48 +05:30
Anoop M D
9ee398dc3b Merge pull request #480 from grubersjoe/gql-validation
Fix GraphQL validation
2023-10-10 00:11:24 +05:30
Anoop M D
c4a21e1089 Merge pull request #492 from lared/bugfix/set-up-ajv-in-tests
fix(#473) add Ajv and node-fetch to the test runtime
2023-10-10 00:08:53 +05:30
Michał Szymborski
2b25a0be18 fix(#473) add Ajv to the test runtime 2023-10-09 20:23:07 +02:00
Anoop M D
524c3f8445 Merge pull request #488 from lared/bugfix/set-up-ajv-in-scripts
fix(#473) add Ajv to the runtime
2023-10-09 22:15:17 +05:30
Michał Szymborski
c67cf6cca2 fix(#473) add Ajv to the runtime 2023-10-09 18:41:29 +02:00
Anoop M D
33da3a1303 Merge pull request #486 from Its-treason/fix/r-replace-not-a-function
fix(#382): TypeError r.replace is not a function
2023-10-09 21:31:33 +05:30
Its-treason
c586deeb30 fix(#382): TypeError r.replace is not a function 2023-10-09 17:56:48 +02:00
Anoop M D
051dac02cd Merge pull request #475 from Sl-Alex/bugfix/assertions-modify-value
Fix assertion value modification
2023-10-09 19:19:29 +05:30
Jonathan Gruber
0668331822 fix(#222): harden content security policy and allow loading inline images 2023-10-09 15:45:06 +02:00
Sebastien Dionne
1affe0056b Add build support for different OS 2023-10-09 08:32:02 -04:00
Lőrik Levente
0ad2186c43 Replace macos icon 2023-10-09 14:10:14 +02:00
Jonathan Gruber
5db50339e0 fix(#222): add missing codemirror/addon/lint/lint import to fix GQL validation 2023-10-09 13:09:45 +02:00
Jonathan Gruber
2df7f63ba9 update package-lock.json 2023-10-09 13:08:34 +02:00
Oleksii Slabchenko
0e1c4008e7 Fix assertion value modification 2023-10-09 11:41:46 +02:00
therealrinku
fdca86ffd2 showing error response in response tab instead of alert message 2023-10-09 12:33:10 +05:45
Mirko Golze
b9291201d9 Merge branch 'main' into feature/proxy-global-and-collection
# Conflicts:
#	package-lock.json
#	packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js
#	packages/bruno-electron/src/ipc/network/index.js
2023-10-09 07:21:25 +02:00
Anoop M D
239caa1763 chore: published bru cli v0.14.0 2023-10-09 08:27:32 +05:30
Anoop M D
362289b7cd feat(#334): bru cli support for collection headers, auth, scripts and tests 2023-10-09 08:26:10 +05:30
Anoop M D
f9f1ca0640 chore: version bump 0.22.0 2023-10-09 07:30:37 +05:30
Anoop M D
53eb53e062 chore: collection and folder as request names are reserved 2023-10-09 07:08:36 +05:30
Anoop M D
2d149d94ef Merge pull request #456 from survivant/feature/request-indexname
Feature/request indexname
2023-10-09 07:00:50 +05:30
Anoop M D
f0c3b8a877 Merge pull request #467 from game5413/enhancement/collection-dropdown
Better experience for open request or folder dropdown action
2023-10-09 06:52:13 +05:30
Anoop M D
3a222458d1 Merge pull request #468 from rqbazan/feat/add-support-for-socks5-proxy
feat: add support for socks5 proxy
2023-10-09 06:40:32 +05:30
Anoop M D
46bc097733 Merge branch 'main' into feat/add-support-for-socks5-proxy 2023-10-09 06:40:12 +05:30
Anoop M D
1ce8d707f1 feat(#334): collection level headers, auth, scripts and tests 2023-10-09 06:18:05 +05:30
Mirko Golze
b854e66a24 #224 refactor preferences store, add global proxy settings 2023-10-08 23:12:03 +02:00
Qweme Dev
1a366893ec Fix styles for theme 2023-10-08 21:53:29 +03:00
Ricardo Q. Bazan
bf90ee96e5 chore: add support for cli 2023-10-08 12:09:33 -05:00
Ricardo Q. Bazan
ce2e997c06 feat: add support for socks5 proxy 2023-10-08 10:04:16 -05:00
game5413
7617ae12cb Merge branch 'main' into enhancement/collection-dropdown 2023-10-08 21:48:36 +07:00
game5413
905e993a13 better experience for open dropdown collection 2023-10-08 21:46:13 +07:00
therealrinku
be6abf421f feat: added active query, headers and asserts count 2023-10-08 19:38:13 +05:45
Jack Scotson
b47b0565e5 Merge branch 'main' into bugfix/fixes-251 2023-10-08 10:52:18 +01:00
Jack Scotson
094bf7bc60 fix(#251): Uses os.release to get correct pathsep for windows 2023-10-08 10:47:34 +01:00
Anoop M D
159dd90b03 fix: fixed issue where window width and height were not set for first time users 2023-10-08 05:26:49 +05:30
Anoop M D
c222bf47c3 Merge pull request #452 from jarne/feature/persist-window-state
Persist window state (size, position)
2023-10-08 04:36:09 +05:30
Anoop M D
c91bc0d1c6 Merge pull request #457 from Sl-Alex/bugfix/between-assert
Fix 'between' assertion
2023-10-08 04:32:12 +05:30
Anoop M D
64ac763a13 Merge pull request #441 from grubersjoe/gql-auth
Improve GQL introspection request
2023-10-08 04:17:00 +05:30
Anoop M D
bca3d5749b Merge pull request #447 from therealrinku/two
minor styling fixes
2023-10-08 04:15:33 +05:30
Anoop M D
2ee6759282 Merge pull request #455 from survivant/patch-1
typo in the error message
2023-10-08 04:10:58 +05:30
Anoop M D
c479c2e786 Merge pull request #459 from lared/feature/show-devtools
fix(#440) open dev tools from menu
2023-10-08 04:10:16 +05:30
Michał Szymborski
6d8bdfa276 fix(#440) open dev tools from menu 2023-10-07 23:42:12 +02:00
Oleksii Slabchenko
024f61a95e Fix 'between' assertion 2023-10-07 22:42:13 +02:00
Anoop M D
856236c918 feat(#334): Bru lang updates for collection.bru file 2023-10-08 01:57:30 +05:30
Sebastien Dionne
0179c58cd2 fix typo and use singular 2023-10-07 15:54:33 -04:00
Sebastien Dionne
515381b930 fix typo and use singular 2023-10-07 15:53:34 -04:00
Sebastien Dionne
5ef0de2e4d Revert "Building electron script in js to support different OS"
This reverts commit ae78ed36ef.
2023-10-07 15:33:38 -04:00
Anoop M D
c6fef2f1be chore: removed legacy code 2023-10-08 01:01:40 +05:30
Sebastien Dionne
21536f9a27 Support request name with "index" in the name if index is not separated 2023-10-07 15:28:17 -04:00
Anoop M D
faf8581ddf feat(#253): hide hotkey 2023-10-08 00:55:09 +05:30
Sebastien Dionne
47992288c4 Use singular instead 2023-10-07 15:04:45 -04:00
Sebastien Dionne
640692abc7 typo in the error message
there was a little typo
2023-10-07 14:59:55 -04:00
Anoop M D
ed95b8349e chore: bump version v0.21.1 2023-10-08 00:05:10 +05:30
Anoop M D
81b1982d4f feat: response overlay polish 2023-10-08 00:03:32 +05:30
Anoop M D
e381d0c00b chore: ui layout polish 2023-10-07 19:12:20 +05:30
Anoop M D
50f2f54335 fix(#150): fixed arrow direction 2023-10-07 19:10:11 +05:30
Jarne
fab350a32d Add some checks if position/size is inside screen border 2023-10-07 14:51:47 +02:00
Jarne
4600217606 Add basic window state persistance 2023-10-07 14:27:24 +02:00
therealrinku
6421148dc1 minor styling fixes 2023-10-07 12:50:19 +05:45
Anoop M D
12f3f802a7 request save control 2023-10-07 09:02:19 +05:30
Sebastien Dionne
ae78ed36ef Building electron script in js to support different OS 2023-10-06 22:17:10 -04:00
Anoop M D
1e27427d09 chore: bump version to 0.21.0 2023-10-07 03:28:11 +05:30
Jonathan Gruber
f2a8bd5d10 chore: update package-lock.json 2023-10-06 23:57:50 +02:00
Anoop M D
860a3b16ad fix(#440): bring back menu bar 2023-10-07 03:26:12 +05:30
Anoop M D
8fadaf98ae feat(#306): bru cli v0.13.0 release 2023-10-07 03:23:39 +05:30
Anoop M D
8f1f41374c feat(#306): module whitelisting support 2023-10-07 03:20:44 +05:30
Anoop M D
e3679c9ee9 feat(#306): module whitelisting support 2023-10-07 03:19:02 +05:30
Jonathan Gruber
7f17999486 fix(#354): add set headers to introspection request 2023-10-06 23:48:40 +02:00
Jonathan Gruber
d2b35beb6f fix(#354): use Handlebars instead of deprecated Mustache 2023-10-06 23:23:57 +02:00
Anoop M D
0f3a8a87bb feat(#306): bru cli support for allowScriptFilesystemAccess 2023-10-07 02:41:56 +05:30
Jonathan Gruber
3c0770b792 fix(#354): also interpolate env vars in introspection request 2023-10-06 23:10:53 +02:00
Anoop M D
5a89d12716 feat: removed legacy v1 bru lang auto migrate functionality 2023-10-07 00:25:26 +05:30
Anoop M D
8730c5a85b Merge pull request #413 from therealrinku/one
request colors made consistent on the sidebar and tabs
2023-10-06 23:34:08 +05:30
Anoop M D
24fcb8caf2 Merge pull request #430 from lared/bugfix/misordered-requests
fix(#428): moving requests into directories or into root of collection broke ordering
2023-10-06 22:58:08 +05:30
Anoop M D
5a0e68073f Merge pull request #436 from not-known-person/fix-/#419
fix-/#419
2023-10-06 22:56:17 +05:30
not-known-person
945f1eb74a fix-/#419 2023-10-06 22:00:20 -04:00
Anoop M D
de74edb50f feat: interpolation of proxy vars 2023-10-06 22:49:48 +05:30
Brian Rodgers
80414d751d Merge branch 'usebruno:main' into main 2023-10-06 12:11:28 -05:00
Brian Rodgers
295d82dca9 Added support for AWS Sig V4 Authentication 2023-10-06 11:51:09 -05:00
Anoop M D
9474918853 chore: fixed typo 2023-10-06 22:11:14 +05:30
Anoop M D
6db36513c5 Merge pull request #434 from oscarpas/fix/graphql-docs-explorer-styling
fix: add missing CSS for GraphQL docs explorer
2023-10-06 21:43:57 +05:30
Oscar Pastarus
7be38bcfe0 fix: add missing CSS for graphql docs explorer 2023-10-06 17:46:09 +02:00
Qweme Dev
289625c851 Added a setting that allows you to change the font of the code 2023-10-06 17:53:05 +03:00
Michał Szymborski
1f5aa25d5e fix(#428): moving requests into directories or into root of collection broke ordering 2023-10-06 16:39:13 +02:00
Brian Rodgers
e2e3895a58 Added support for AWS Sig V4 Authentication 2023-10-06 08:47:05 -05:00
Anoop M D
65e448b1eb Merge pull request #374 from acostinescu/main
Changed Proxied Requests to Use https-proxy-agent/http-proxy-agent.
2023-10-06 17:55:39 +05:30
Anoop M D
d5a6522563 Merge branch 'main' into main 2023-10-06 17:36:35 +05:30
therealrinku
be72d24ecb request colors made consistent on the sidebar and tabs 2023-10-06 14:57:20 +05:45
Anoop M D
c25542bbdf chore: bru cli v0.12.0 release 2023-10-06 04:59:57 +05:30
Anoop M D
a838185ddf chore: version bump v0.20.0 2023-10-06 04:18:44 +05:30
Anoop M D
ac3637fcfa Merge pull request #404 from qweme32/feature/russian-localization
Add Russian localization
2023-10-06 04:15:22 +05:30
Anoop M D
e3f32dfc87 Merge pull request #397 from dozed/response-time-axios
Use axios interceptor to measure response time
2023-10-06 04:13:52 +05:30
Anoop M D
a204e3814b Merge pull request #400 from lared/bug/env-with-empty-secret-variable
fix(#399): loading environment with empty secret variable fails
2023-10-06 04:11:57 +05:30
Anoop M D
be8db1876a feat(#111): refactor new request auto open logic 2023-10-06 04:02:51 +05:30
Qweme Dev
64d9413777 Add Russian localization 2023-10-05 22:47:14 +03:00
Michał Szymborski
a71afc8f73 fix(#399): loading environment with empty secret variable fails 2023-10-05 19:38:17 +02:00
Anoop M D
4e4130acf5 Merge pull request #373 from Its-treason/feature/auto-open-new-request
feat(#111): Automaticly open a newly created request
2023-10-05 21:22:56 +05:30
Stefan Ollinger
bb7d13d2d9 Use axios interceptor to measure response time 2023-10-05 17:46:12 +02:00
Anoop M D
c08be14d87 Merge branch 'main' of github.com:usebruno/bruno 2023-10-05 21:15:59 +05:30
Anoop M D
90f47e5877 feat(#396): decomment support in json and scripts 2023-10-05 21:15:38 +05:30
Its-treason
8a19189dcd feat(#111): Use existing last actions for auto open tabs 2023-10-05 17:43:17 +02:00
Anoop M D
efbc119e7c Merge pull request #395 from dozed/fix-show-response-time
Put constant in outer scope
2023-10-05 20:50:00 +05:30
Anoop M D
d36956e0a4 Merge pull request #393 from zyrouge/linux-qol
Better BrowserWindow
2023-10-05 20:41:31 +05:30
Stefan Ollinger
efbdde8252 Set responseTime 2023-10-05 17:11:29 +02:00
Stefan Ollinger
ae2f170fe6 Put constant in outer scope 2023-10-05 17:05:31 +02:00
zyrouge
4f0d87fba4 refactor: set title 2023-10-05 20:29:50 +05:30
zyrouge
8703124007 refactor: enable auto hide menu bar 2023-10-05 20:24:02 +05:30
zyrouge
f9f99adbf9 refactor: set icon of windows 2023-10-05 20:23:33 +05:30
Anoop M D
da9a6c434a Merge pull request #390 from grubersjoe/gql-auth
feat: Add authentication for GraphQL requests
2023-10-05 20:21:58 +05:30
Anoop M D
c8764f6555 Merge pull request #391 from dozed/show-response-time
Show response time in milliseconds per request and total
2023-10-05 20:17:58 +05:30
Stefan Ollinger
e90718f47b Show response time in milliseconds per request and total 2023-10-05 16:42:49 +02:00
Jonathan Gruber
af130bac17 feat(usebruno#354): Add authentication for GraphQL requests
Schema introspection query will also use specified authentication.
2023-10-05 16:37:05 +02:00
Stefan Ollinger
04481a26e1 Show response time in milliseconds per request and total 2023-10-05 16:36:57 +02:00
Anoop M D
86c2a60184 Merge pull request #380 from petoc/bugfix/insomnia-importer
updated insomnia importer
2023-10-05 19:54:06 +05:30
Anoop M D
0cbf803ed9 chore: restructured tests into seperate dir as jest error was preventing local exec of cli 2023-10-05 19:51:06 +05:30
Anoop M D
c91819c072 Merge branch 'main' of github.com:usebruno/bruno 2023-10-05 19:40:44 +05:30
Anoop M D
2c4a3a5eb4 Merge pull request #241 from tpyle/bug/correct-result-reporting
Handle failed requests and reduce duplication
2023-10-05 19:40:28 +05:30
Anoop M D
99fc980a48 chore: updated readme 2023-10-05 19:30:38 +05:30
Anoop M D
960f62ac8e chore: updated readme 2023-10-05 15:44:34 +05:30
Peter C.
57e0f0c0c4 updated insomnia importer 2023-10-05 09:45:08 +02:00
Alex Costinescu
8216bf5eec Merge remote-tracking branch 'upstream/main' 2023-10-04 20:37:18 -04:00
Alex Costinescu
9f11cfc836 Changed proxied requests to use https-proxy-agent to handle TCP tunneling. 2023-10-04 20:31:34 -04:00
Its-treason
51cb930b6a feat(#111): Automaticly open a newly created request 2023-10-05 01:57:02 +02:00
Anoop M D
1c8712b6eb chore: bru cli v0.11.0 release 2023-10-05 04:04:05 +05:30
Anoop M D
6c1f8c78b2 chore: version bumped to v0.19.0 2023-10-05 03:47:43 +05:30
Anoop M D
b35b814561 chore: added package-lock.json 2023-10-05 03:46:34 +05:30
Anoop M D
80eaaad658 Merge pull request #356 from therealrinku/main
github star button color fix
2023-10-05 03:35:38 +05:30
Anoop M D
51a73d864e Merge pull request #368 from luizfonseca/fix/use-correct-duration-axios-interceptor
fix(network): use axios interceptors to better reflect durations
2023-10-05 03:34:34 +05:30
Anoop M D
87e29db545 Merge pull request #370 from VersusF/feature/copy-environment
Feature: Add new button to copy existing environments
2023-10-05 03:29:31 +05:30
Anoop M D
0cf18e6804 feat(#306): allow fs access inside scripts 2023-10-05 03:20:53 +05:30
Filippo Contro
3a6dacc1f4 Add: Add new button to copy existing environments 2023-10-04 23:32:02 +02:00
Anoop M D
8d89496304 fix(#329): fixed basic auth header issue + added cli support for basic and bearer auth 2023-10-05 02:32:24 +05:30
Luiz Fonseca
6ef7891f8a fix(network): use interceptors when measuring request duration 2023-10-04 22:09:48 +02:00
Luiz Fonseca
e1bf475f1f chore: pretty-quick fixes 2023-10-04 22:09:11 +02:00
Luiz Fonseca
f7eb325e11 fix: husky pre-commit missing correct prettier version 2023-10-04 22:08:25 +02:00
Luiz Fonseca
c50b88d60d fix: missing typescript module on npm build script 2023-10-04 22:07:26 +02:00
Anoop M D
90fedc8ec6 fix(#334): fixed yaml response parsing issue 2023-10-04 22:55:17 +05:30
therealrinku
9c46bc79d9 github star button color fix 2023-10-04 22:14:34 +05:45
Anoop M D
9ecfb3a640 chore: added testimnoial link in readme 2023-10-04 18:14:45 +05:30
Thomas Pyle
da4e96d1ef Merge remote-tracking branch 'upstream/main' into bug/correct-result-reporting 2023-10-03 22:15:53 -04:00
Anoop M D
bf2d1220a1 chore: version bumped to v0.18.0 2023-10-04 05:53:59 +05:30
Anoop M D
744abe585c revert pr #252 2023-10-04 05:52:59 +05:30
Anoop M D
5e2640fe73 fix: fixed query result issue 2023-10-04 05:48:05 +05:30
Anoop M D
7513314179 Merge branch 'main' of github.com:usebruno/bruno 2023-10-04 04:20:52 +05:30
Anoop M D
d4b663bfa8 Merge pull request #252 from acostinescu/main
Changed Proxied Requests to Use https-proxy-agent.
2023-10-04 04:20:38 +05:30
Anoop M D
9b2e2ed3d8 chore: style updates 2023-10-04 04:11:23 +05:30
Anoop M D
e4ea6b9109 Merge pull request #299 from ilumin/feature/double-click-rename
feat(291): add double click request to open rename modal
2023-10-04 03:40:29 +05:30
Anoop M D
1a8feb8029 Merge pull request #282 from Its-treason/feature/preview-response-html
feature/preview-response-html
2023-10-04 03:40:11 +05:30
Anoop M D
72a5f05009 Merge pull request #302 from j0k3r/bugfix/reponse-size-two-decimal
Fix rounding response size
2023-10-04 03:32:30 +05:30
Anoop M D
d5ef240de6 Merge pull request #308 from Its-treason/bugfix/assert-not-being-removed
fix(#270): Fix deleted assertions & tests shown in test tab
2023-10-04 03:29:44 +05:30
Anoop M D
1932d98b57 Merge pull request #321 from lared/bugfix/tabbing-order
fix(#314): tabbing order through tables (variables etc) was inconsistent or impossible
2023-10-04 03:26:00 +05:30
Anoop M D
bdb7ff9947 chore: style updates 2023-10-04 03:07:50 +05:30
Anoop M D
3f4f7fb24c feat(#119): basic auth support completed 2023-10-04 02:58:22 +05:30
Michał Szymborski
494b484cd9 fix(#312): tabbing order through tables (variables etc) was inconsistent or impossible
resolves #312
2023-10-03 21:36:31 +02:00
Anoop M D
48e4e60696 feat(#119): bearer auth support completed 2023-10-04 00:36:52 +05:30
Anoop M D
2e600838b2 Merge branch 'main' of github.com:usebruno/bruno 2023-10-03 22:48:56 +05:30
Anoop M D
52428c6b35 Merge pull request #318 from usebruno/feature/auth-support
Feature/auth support
2023-10-03 22:47:22 +05:30
Its-treason
2734f26c78 fix(#270): Fix deleted assertions & tests shown in test tab
After all tests or assertions were deleted the store was not
updated because of the length checks. Now the assertions/tests
will always try to run, to ensure the data is always updated.
2023-10-03 13:04:34 +02:00
Jeremy Benoist
73c62010b7 Fix rounding response size
I noticed that sometimes the response size is weirdly displayed.
- size `112932` is displayed as `110.28.999999999999996KB`
- size `112990` is displayed as `110.34KB`

The problem is in the decimal calculation. Rounding the value ensure we don't have two `.` in the displayed size.

Also, update the `contributing` to increase the minimum Node version required.
2023-10-03 10:39:38 +02:00
O Teerasak Vichadee
26853787da feat(291): add double click request to open rename modal 2023-10-03 11:28:27 +07:00
Anoop M D
712319ff34 Merge pull request #296 from Its-treason/bugfix/modal-text-color
fix: Color in environment delete modal
2023-10-03 04:36:21 +05:30
Its-treason
7a80247fc5 fix: Color in environment delete modal 2023-10-03 00:48:49 +02:00
Its-treason
ea9e294c54 feat(#245): Update tab design + Remove CSP 2023-10-02 19:50:01 +02:00
Anoop M D
55315b5b88 Merge pull request #288 from Cibico99/feature/xml-format-option-fix
Fix for XML Formatting Options
2023-10-02 20:34:10 +05:30
pedward99
d63e7371fe Fix for XML Formatting Options 2023-10-02 10:39:29 -04:00
Its-treason
dce11d1bd5 Merge branch 'main' of github.com:usebruno/bruno into feature/preview-response-html 2023-10-02 14:34:54 +02:00
Its-treason
e720fed63b feat(#245): Add HTML preview to response 2023-10-02 14:26:24 +02:00
Anoop M D
43f7c2ab86 chore: version bump to v0.17.0 2023-10-02 16:55:22 +05:30
Anoop M D
95532102ba feat(#280): code generator polishing and support url interpolation 2023-10-02 16:52:02 +05:30
Anoop M D
4603ec4d5e Merge pull request #280 from Beedhan/feature/code-generator
Feature/code generator
2023-10-02 15:42:07 +05:30
Anoop M D
cbdd56e577 chore: added some todo's for future code cleanup 2023-10-02 15:38:50 +05:30
Anoop M D
ce77d08408 Merge branch 'main' of github.com:usebruno/bruno 2023-10-02 15:32:39 +05:30
Anoop M D
3a5071412e Merge pull request #277 from not-known-person/feat-/#220-Improvement
Improved feat(#220)
2023-10-02 15:32:26 +05:30
not-known-person
336ad38cb9 Improved feat-/#220 2023-10-02 08:34:39 -04:00
Anoop M D
636e14a385 feat(#253): released cmd+h key back to native mac hide behaviour 2023-10-02 15:28:54 +05:30
Anoop M D
bfc03f5ae4 fix(#279): fixed codemirror crashes resulting in white screen 2023-10-02 15:25:59 +05:30
Beedhan
97200961eb feat: support for variables in url 2023-10-02 14:24:13 +05:45
Anoop M D
7297bb184e chore: updated readme 2023-10-02 13:58:09 +05:30
Beedhan
b74922c8f3 bugfix:sidebar moving when code generator modal is opened 2023-10-02 14:02:54 +05:45
Anoop M D
ce0827308f chore: updated readme 2023-10-02 13:47:14 +05:30
Beedhan
3d0c9cc0ae feat:added code generator to http-requests 2023-10-02 13:58:25 +05:45
Anoop M D
1804454ff0 chore: bump version v0.16.6 2023-10-02 05:26:47 +05:30
Anoop M D
3c710120b9 Merge pull request #274 from lared/bugfix/drag-and-drop-ordering
fix(#154): correct ordering during drag-and-drop
2023-10-02 04:48:43 +05:30
Anoop M D
fcc12fb089 fix(#251, #265): phantoms folders fix on rename/delete needs to be run only on windows 2023-10-02 04:17:35 +05:30
Anoop M D
3d8dee944f Merge pull request #266 from not-known-person/fix-251-265
fixed issue-#251-&-#265,
2023-10-02 04:03:31 +05:30
Anoop M D
78e5cd3c03 Merge pull request #264 from Its-treason/bugfix/insomnia-import-name-collision
fix(#257): Name collision during Insomnia collection import
2023-10-02 03:37:40 +05:30
Its-treason
26d99c7aee fix(#257): Name collision during Insomnia collection import 2023-10-01 23:47:43 +02:00
Anoop M D
77a7318dfb Merge pull request #272 from not-known-person/feat/-#220
added feat/-#220
2023-10-02 02:55:24 +05:30
not-known-person
b83da46f12 added feat/-#220 2023-10-01 21:01:39 -04:00
not-known-person
cf6ec4e84f fixed issue-#251-&-#265 2023-10-01 15:22:05 -04:00
Michał Szymborski
978d810473 fix(#154): correct ordering during drag-and-drop
When dragging and dropping items, to delay changing sequence numbers
until after all the resource-dependent logic has completed, we were
relying on the order of children in redux store (which we then converted
into new seq numbers).

This order of children was however not updated when sequence numbers
changed (for example due to file watch changes). This resulted in a
seemingly random drag-and-drop ordering, which in fact was linked to the
initial order when the collection was loaded.

This change sorts all the items by sequence number prior to reordering,
so that those random jumps no longer happen. As this happens on a deep
clone of the collection, no data gets hurt in the process.

fixes #154
2023-10-01 21:09:11 +02:00
Beedhan
cedcd2cf35 Revert "fix: folder showing after deleting"
This reverts commit ce9cdc5293.
2023-10-02 00:00:03 +05:45
Beedhan
ce9cdc5293 fix: folder showing after deleting 2023-10-01 23:55:39 +05:45
Anoop M D
e83c2da798 chore: version bumped to v0.16.5 2023-10-01 02:17:28 +05:30
Anoop M D
39f148267e Merge pull request #262 from mirkogolze/feature/about-window
#203 #254 #231 add icon to asar content, change styling to allow copying the content
2023-10-01 01:51:32 +05:30
Mirko Golze
f4f093d4db #203 #254 #231
add icon to asar content, change styling to allow copying the content
2023-09-30 21:56:21 +02:00
Anoop M D
3c4ef2f2df Merge pull request #256 from brahma-dev/main
Ensure that adjacent variables are rendered in separate spans.
2023-10-01 00:19:46 +05:30
Anoop M D
e39975cb3c Merge pull request #259 from sadn1ck/fix/gql-docs-build-error
fix(graphql-docs/build): rollup error thrown during build
2023-10-01 00:12:12 +05:30
Anoop M D
b767ccd063 Merge pull request #260 from Its-treason/feature/update-create-collection-form
feat: Update create collection form
2023-09-30 22:09:54 +05:30
Anoop M D
c5adfd8975 Merge pull request #261 from andypiper/bugfix/grammar-typo
Remove rogue apostrophes and capitalise API in text.
2023-09-30 22:05:01 +05:30
Andy Piper
6695d90609 Remove rogue apostrophes and capitalise API in text.
Signed-off-by: Andy Piper <andypiper@users.noreply.github.com>
2023-09-30 15:37:45 +01:00
Its-treason
5c79282a1b feat: Update create collection form
- Remove Name tooltip
- Update Folder Name tooltip
- Move Folder Name input under location
- Update Folder Name validation
  - Now only allow characters for valid system folder names
- Update label htmlFor ids to input ids
2023-09-30 14:35:37 +02:00
Anik Das
64019f8ecf fix(graphql-docs/build): rollup error thrown during build
- during the dts transformation, the css import was not recognized, hence marking it as external in the dts transform didn't throw the error

- "extract: true" in postcss plugin makes sure it gets extracted to the final bundle as well

Signed-off-by: Anik Das <anikdas0811@gmail.com>
2023-09-30 09:49:02 +05:30
Brahma Dev
4c89f31934 Decrease likelihood of any unintentional classname clash. 2023-09-29 21:58:46 +00:00
Brahma Dev
1c53ce91f0 Ignore the randomly generated classname. 2023-09-29 21:42:15 +00:00
Brahma Dev
0b83fbb7ce Add a randomly generated classname to each variable so that CodeMirror does not merge adjacent variables into the same SPAN. 2023-09-29 21:41:54 +00:00
Anoop M D
08ceed86a8 chore: bump version to v0.16.4 2023-09-30 02:48:20 +05:30
Anoop M D
f99918d725 fix(#229): fixed handling of non-JSON/XML content types 2023-09-30 02:44:30 +05:30
Anoop M D
1877dd858e chore: remove unused var 2023-09-30 02:42:10 +05:30
Anoop M D
acff0c379e fix(#236): insomnia import fix 2023-09-30 02:24:31 +05:30
Anoop M D
a849e4fb7b Merge pull request #255 from Its-treason/bugfix/create-collection-modal-validation
Bugfix: Create collection modal validation
2023-09-30 01:53:32 +05:30
Anoop M D
613699fb69 fix(#248): gracefully abort cm header hint when word match fails 2023-09-30 01:44:54 +05:30
Anoop M D
0c4ba71922 feat(#229): added error boundary to capture error trace and allow users to continue using the app 2023-09-30 01:32:05 +05:30
Its-treason
d346bb00eb Merge branch 'main' into bugfix/create-collection-modal-validation 2023-09-29 21:45:01 +02:00
Its-treason
8bb57aa41d fix: location validation in create collection form 2023-09-29 21:44:37 +02:00
Anoop M D
f378f04fc3 Merge pull request #248 from brahma-dev/main
Autocomplete
2023-09-30 00:35:22 +05:30
Alex Costinescu
742d69b03c Re-added missing https require statement. 2023-09-29 15:37:39 +02:00
Alex Costinescu
0e3bc62d9d Changed proxied requests to use https-proxy-agent to handle TCP tunneling. 2023-09-29 15:24:13 +02:00
Brahma Dev
a02d2b9c58 Add standard http headers for autocomplete 2023-09-29 11:59:09 +00:00
Brahma Dev
21edfbc25a Use Codemirror Hint feature for autocomplete 2023-09-29 11:59:01 +00:00
Thomas Pyle
4ba4d8fc27 Merge branch 'main' into bug/correct-result-reporting 2023-09-29 06:53:57 -05:00
Thomas Pyle
5f32924ddb Adds some simple unit tests around printRunSummary 2023-09-28 20:58:25 -04:00
Thomas Pyle
9bcf56d689 Handle failed requests and reduce duplication 2023-09-28 20:42:48 -04:00
Anoop M D
0f1e330dc5 feat(#119): ui placeholders for basic and bearer auths 2023-09-29 02:11:04 +05:30
Anoop M D
51ee37cf96 feat(#119): bru lang support for basic and bearer auth 2023-09-29 01:35:22 +05:30
245 changed files with 39133 additions and 2307 deletions

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
# Description
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
### Contribution Checklist:
- [ ] **The pull request only addresses one issue or adds one feature.**
- [ ] **The pull request does not introduce any breaking changes**
- [ ] **I have added screenshots or gifs to help explain the change if applicable.**
- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).**
- [ ] **Create an issue and link to the pull request.**
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
### Publishing to New Package Managers
Please see [here](../publishing.md) for more information.

50
.github/workflows/release-snap.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Publish to Snapcraft
on:
workflow_dispatch:
inputs:
build:
description: 'Build and publish to Snapcraft'
required: true
default: 'true'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Check package-lock.json
run: npm ci
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Build Electron app
run: |
npm run build:bruno-query
npm run build:graphql-docs
npm run build:web
npm run build:electron:snap
- name: Install Snapcraft
run: |
sudo snap install snapcraft --classic
continue-on-error: true
- name: Configure Snapcraft
run: snapcraft whoami
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_API_KEY }}
- name: Publish to Snapcraft
run: |
snapcraft upload --release=stable packages/bruno-electron/out/*.snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_API_KEY }}

View File

@@ -5,16 +5,16 @@ on:
pull_request:
branches: [main]
jobs:
test:
tests:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 16
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm i --legacy-peer-deps
run: npm ci --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
@@ -27,5 +27,19 @@ jobs:
run: npm run test --workspace=packages/bruno-app
- name: Test Package bruno-js
run: npm run test --workspace=packages/bruno-js
- name: Test Package bruno-cli
run: npm run test --workspace=packages/bruno-cli
- name: Test Package bruno-electron
run: npm run test --workspace=packages/bruno-electron
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci --legacy-peer-deps
- name: Run Prettier
run: npm run test:prettier:web

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@
node_modules
yarn.lock
pnpm-lock.yaml
package-lock.json
.pnp
.pnp.js

View File

@@ -1,10 +1,12 @@
## Lets make bruno better, together !!
**English** | [Українська](docs/contributing/contributing_ua.md) | [Русский](docs/contributing/contributing_ru.md) | [Türkçe](docs/contributing/contributing_tr.md) | [Deutsch](docs/contributing/contributing_de.md) | [Français](docs/contributing/contributing_fr.md)
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
## Let's make bruno better, together !!
We are happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
### Technology Stack
Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections)
Bruno is built using Next.js and React. We also use electron to ship a desktop version (that supports local collections)
Libraries we use
@@ -19,11 +21,61 @@ Libraries we use
### Dependencies
You would need [Node v14.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
### Lets start coding
## Development
Please reference [development.md](docs/development.md) for instructions on running the local development environment.
Bruno is being developed as a desktop app. You need to load the app by running the Next.js app in one terminal and then run the electron app in another terminal.
### Dependencies
- NodeJS v18
### Local Development
```bash
# use nodejs 18 version
nvm use
# install deps
npm i --legacy-peer-deps
# build graphql docs
npm run build:graphql-docs
# build bruno query
npm run build:bruno-query
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
### Troubleshooting
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Testing
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```
### Raising Pull Request
@@ -31,5 +83,5 @@ Please reference [development.md](docs/development.md) for instructions on runni
- Please follow the format of creating branches
- feature/[feature name]: This branch should contain changes for a specific feature
- Example: feature/dark-mode
- bugfix/[bug name]: This branch should container only bug fixes for a specific bug
- bugfix/[bug name]: This branch should contain only bug fixes for a specific bug
- Example bugfix/bug-1

View File

@@ -0,0 +1,89 @@
## Lass uns Bruno noch besser machen, gemeinsam !!
Ich freue mich, dass Du Bruno verbessern möchtest. Hier findest Du eine Anleitung, mit der Du Bruno auf Deinem Computer einrichten kannst.
### Technologie Stack
Bruno ist mit Next.js und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt).
Bibliotheken die wir benutzen
- CSS - Tailwind
- Code Editoren - Codemirror
- State Management - Redux
- Icons - Tabler Icons
- Formulare - formik
- Schema Validierung - Yup
- Request Client - axios
- Dateisystem Watcher - chokidar
### Abhängigkeiten
Du benötigst [Node v18.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
### Lass uns coden
Eine Anleitung zum Ausführen einer lokalen Entwicklungsumgebung findest Du in [development.md](docs/development_de.md).
### Pull Request erstellen
- Bitte halte die PRs klein und begrenzt auf eine Sache
- Bitte halte Dich beim Erstellen eines Branches an das folgende Format
- feature/[feature name]: Dieser Branch soll Änderungen für ein bestimmtes Feature enthalten
- Beispiel: feature/dark-mode
- bugfix/[bug name]: Dieser Branch soll ausschließlich Bugfixes für einen bestimmten Bug enthalten
- Beispiel: bugfix/bug-1
## Entwicklung
Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die Next.js App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App.
### Abhängigkeiten
- NodeJS v18
### Lokales Entwickeln
```bash
# use nodejs 18 version
nvm use
# install deps
npm i --legacy-peer-deps
# build graphql docs
npm run build:graphql-docs
# build bruno query
npm run build:bruno-query
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
### Troubleshooting
Es kann sein, dass Du einen `Unsupported platform`-Fehler bekommst, wenn Du `npm install` ausführst. Um dies zu beheben, musst Du `node_modules` und `package-lock.json` löschen und `npm install` erneut ausführen. Dies sollte alle notwendigen Pakete installieren, die zum Ausführen der Anwendung benötigt werden.
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Testen
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

@@ -0,0 +1,89 @@
## Ensemble, améliorons Bruno !
Je suis content de voir que vous envisagez améliorer Bruno. Ci-dessous, vous trouverez les règles et guides pour récupérer Bruno sur votre ordinateur.
### Technologies utilisées
Bruno est construit en utilisant NextJs et React. Nous utilisons aussi Electron pour embarquer la version ordinateur (qui permet les collections locales).
Les bibliothèques que nous utilisons :
- CSS - Tailwind
- Code Editors - Codemirror
- State Management - Redux
- Icons - Tabler Icons
- Forms - formik
- Schema Validation - Yup
- Request Client - axios
- Filesystem Watcher - chokidar
### Dépendances
Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
### Commençons à coder
Veuillez vous référez à la [documentation de développement](docs/development_fr.md) pour les instructions de démarrage de l'environnement de développement local.
### Ouvrir une Pull Request
- Merci de conserver les PR petites et focalisées sur un seul objectif
- Merci de suivre le format de nom des branches
- feature/[feature name]: Cette branche devrait contenir une fonctionnalité spécifique
- Exemple: feature/dark-mode
- bugfix/[bug name]: Cette branche devrait contenir seulement une solution pour pour une bogue spécifique
- Exemple: bugfix/bug-1
## Développement
Bruno est développé comme une application de _lourde_. Vous devez charger l'application en démarrant nextjs dans un terminal, puis démarre l'application Electron dans un autre terminal.
### Dépendances
- NodeJS v18
### Développement local
```bash
# use nodejs 18 version
nvm use
# install deps
npm i --legacy-peer-deps
# build graphql docs
npm run build:graphql-docs
# build bruno query
npm run build:bruno-query
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
### Dépannage
Vous pourriez rencontrer une error `Unsupported platform` pendant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules`, le fichier `package-lock.json` et lancer à nouveau `npm install`. Cela devrait isntaller tous les paquets nécessaires pour lancer l'application.
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Tests
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

@@ -0,0 +1,89 @@
## Давайте вместе сделаем Бруно лучше!!!
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
### Стек
Bruno построен с использованием Next.js и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
Библиотеки, которые мы используем
- CSS - Tailwind
- Редакторы кода - Codemirror
- Управление состоянием - Redux
- Иконки - Tabler Icons
- Формы - formik
- Валидация схем - Yup
- Запросы клиента - axios
- Наблюдатель за файловой системой - chokidar
### Зависимости
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
### Приступим к коду
Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки.
### Создание Pull Request
- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи
- Пожалуйста, соблюдайте формат создания веток
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
- Пример: feature/dark-mode
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
- Пример bugfix/bug-1
## Разработка
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение Next.js в одном терминале, а затем запустить приложение electron в другом терминале.
### Зависимости
- NodeJS v18
### Локальная разработка
```bash
# используйте nodejs 18 версии
nvm use
# установите зависимости
npm i --legacy-peer-deps
# билд документации по graphql
npm run build:graphql-docs
# билд bruno query
npm run build:bruno-query
# запустить next приложение ( терминал 1 )
npm run dev:web
# запустить приложение electron ( терминал 2 )
npm run dev:electron
```
### Устранение неисправностей
При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения.
```shell
# Удаление node_modules в подкаталогах
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Удаление package-lock в подкаталогах
find . -type f -name "package-lock.json" -delete
```
### Тестирование
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

@@ -0,0 +1,35 @@
## Bruno'yu birlikte daha iyi hale getirelim !!
Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır.
### Kullanılan Teknolojiler
Bruno, Next.js ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz
Kullandığımız kütüphaneler
- CSS - Tailwind
- Kod Düzenleyiciler - Codemirror
- Durum Yönetimi - Redux
- Iconlar - Tabler Simgeleri
- Formlar - formik
- Şema Doğrulama - Yup
- İstek İstemcisi - axios
- Dosya Sistemi İzleyicisi - chokidar
### Bağımlılıklar
[Node v18.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz
### Kodlamaya başlayalım
Yerel geliştirme ortamının çalıştırılmasına ilişkin talimatlar için lütfen [development.md](docs/development.md) adresine başvurun.
### Pull Request Oluşturma
- Lütfen PR'ları küçük tutun ve tek bir şeye odaklanın
- Lütfen şube oluşturma formatını takip edin
- feature/[özellik adı]: Bu dal belirli bir özellik için değişiklikler içermelidir
- Örnek: feature/dark-mode
- bugfix/[hata adı]: Bu dal yalnızca belirli bir hata için hata düzeltmelerini içermelidir
- Örnek bugfix/bug-1

View File

@@ -0,0 +1,89 @@
## Давайте зробимо Bruno краще, разом !!
Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері.
### Стек технологій
Bruno побудований на Next.js та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron
Бібліотеки, які ми використовуємо
- CSS - Tailwind
- Редактори коду - Codemirror
- Керування станом - Redux
- Іконки - Tabler Icons
- Форми - formik
- Валідація по схемі - Yup
- Клієнт запитів - axios
- Спостерігач за файловою системою - chokidar
### Залежності
Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
### Починаєм писати код
Будь ласка, зверніться до [development_ua.md](docs/development_ua.md) за інструкціями щодо запуску локального середовища розробки.
### Створення Pull Request-ів
- Будь ласка, робіть PR-и маленькими і сфокусованими на одній речі
- Будь ласка, слідуйте формату назв гілок
- feature/[назва feature]: Така гілка має містити зміни лише щодо конкретної feature
- Приклад: feature/dark-mode
- bugfix/[назва баґу]: Така гілка має містити лише виправлення конкретного багу
- Приклад: bugfix/bug-1
## Розробка
Bruno розробляється як декстопний застосунок. Вам потрібно запустити Next.js в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу.
### Залежності
- NodeJS v18
### Локальна розробка
```bash
# Використовуйте nodejs 18-ї версії
nvm use
# встановіть залежності
npm i --legacy-peer-deps
# зберіть документацію graphql
npm run build:graphql-docs
# зберіть bruno query
npm run build:bruno-query
# запустіть додаток next (термінал 1)
npm run dev:web
# запустіть додаток електрон (термінал 2)
npm run dev:electron
```
### Усунення несправностей
Ви можете зтикнутись із помилкою `Unsupported platform` коли запускаєте `npm install`. Щоб усунути цю проблему, вам потрібно видалити `node_modules` та `package-lock.json`, і тоді запустити `npm install`. Це має встановити всі потрібні для запуску додатку пекеджі.
```shell
# Видаліть node_modules в піддиректоріях
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Видаліть package-lock в піддиректоріях
find . -type f -name "package-lock.json" -delete
```
### Тестування
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

@@ -1,53 +0,0 @@
## Development
Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
### Dependencies
* NodeJS v18
### Local Development
```bash
# use nodejs 18 version
nvm use
# install deps
npm i --legacy-peer-deps
# build graphql docs
# note: you can for now ignore the error thrown while building the graphql docs
npm run build:graphql-docs
# build bruno query
npm run build:bruno-query
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
### Troubleshooting
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Testing
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

93
docs/readme/readme_de.md Normal file
View File

@@ -0,0 +1,93 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - Opensource IDE zum Erkunden und Testen von APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.
Bruno speichert Deine Sammlungen direkt in einem Ordner in Deinem Dateisystem. Wir verwenden eine einfache Textauszeichnungssprache - Bru - um Informationen über API-Anfragen zu speichern.
Du kannst Git oder eine andere Versionskontrolle deiner Wahl verwenden, um an deinen API-Sammlungen gemeinsam mit anderen zu arbeiten.
Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno eine Cloud-Synchronisation hinzuzufügen. Wir schätzen den Schutz Deiner Daten und glauben, dass sie auf Deinem Gerät bleiben sollten. Lies unsere Langzeit-Vision [hier](https://github.com/usebruno/bruno/discussions/269).
![bruno](/assets/images/landing-2.png) <br /><br />
### Einsatz auf verschiedensten Plattformen 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Zusammenarbeiten mit Git 👩‍💻🧑‍💻
oder eine Versionskontrolle Deiner Wahl
![bruno](/assets/images/version-control.png) <br /><br />
### Wichtige Links 📌
- [Unsere Langzeit-Vision](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
- [Dokumentation](https://docs.usebruno.com)
- [Webseite](https://www.usebruno.com)
- [Preise](https://www.usebruno.com/pricing)
- [Download](https://www.usebruno.com/downloads)
### Showcase 🎥
- [Erfahrungsberichte](https://github.com/usebruno/bruno/discussions/343)
- [Wissenswertes](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Unterstützung ❤️
Wuff! Wenn Du dieses Projekt magst, klick den ⭐ Button !!
### Teile Erfahrungsberichte 📣
Wenn Bruno Dir bei Deiner Arbeit und in Deinen Teams geholfen hat, vergiss bitte nicht, Deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
### Veröffentlichung in neuen Paketmanagern
Bitte [hier](/publishing.md) für mehr Informationen lesen.
### Mitmachen 👩‍💻🧑‍💻
Ich freue mich, dass Du Bruno verbessern willst. Bitte schau Dir den [Leitfaden zum Mitmachen](../contributing/contributing_de.md) an.
Auch wenn Du nicht in der Lage bist, einen Beitrag in Form von Code zu leisten, zögere bitte nicht, uns Fehler und Funktionswünsche mitzuteilen, die implementiert werden müssen, um Deinen Anwendungsfall zu unterstützen.
### Autoren
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### In Verbindung bleiben 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[Webseite](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Markenzeichen
**Name**
`Bruno` ist ein Markenzeichen von [Anoop M D](https://www.helloanoop.com/)
**Logo**
Das Logo stammt von [OpenMoji](https://openmoji.org/library/emoji-1F436/). Lizenz: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### Lizenz 📄
[MIT](/license.md)

94
docs/readme/readme_fr.md Normal file
View File

@@ -0,0 +1,94 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE Opensource pour explorer et tester des APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _status quo_ que représente Postman et les autres outils.
Bruno sauvegarde vos collections directement sur votre système de fichiers. Nous utilisons un langage de balise de type texte pour décrire les requêtes API.
Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler de manière collaborative sur vos collections d'APIs.
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
![bruno](/assets/images/landing-2.png) <br /><br />
### Fonctionne sur de multiples platformes 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Collaborer via Git 👩‍💻🧑‍💻
Ou n'importe quel système de gestion de sources
![bruno](/assets/images/version-control.png) <br /><br />
### Liens importants 📌
- [Notre vision à long terme (en anglais)](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
- [Documentation](https://docs.usebruno.com)
- [Site web](https://www.usebruno.com)
- [Prix](https://www.usebruno.com/pricing)
- [Téléchargement](https://www.usebruno.com/downloads)
### Showcase 🎥
- [Témoignages](https://github.com/usebruno/bruno/discussions/343)
- [Centre de connaissance](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Soutien ❤️
Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !!
### Partage de témoignages 📣
Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion Github dédiée](https://github.com/usebruno/bruno/discussions/343)
### Publier Bruno sur un nouveau gestionnaire de paquets
Veuillez regarder [ici](/publishing.md) pour plus d'information.
### Contribuer 👩‍💻🧑‍💻
Je suis heureux de voir que vous cherchez à améliorer Bruno. Merci de consulter le [guide de contribution](../contributing/contributing_fr.md)
Même si vous n'êtes pas en mesure de contribuer directement via du code, n'hésitez pas à consigner les bogues et les demandes de nouvelles fonctionnalités pour résoudre vos cas d'usage !
### Auteurs
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### Restons en contact 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Marque
**Nom**
`Bruno` est une marque appartenant à [Anoop M D](https://www.helloanoop.com/)
**Logo**
Le logo est issu de [OpenMoji](https://openmoji.org/library/emoji-1F436/).
Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### Licence 📄
[MIT](/license.md)

77
docs/readme/readme_ru.md Normal file
View File

@@ -0,0 +1,77 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
Bruno хранит ваши коллекции непосредственно в папке в вашей файловой системе. Для сохранения информации об API-запросах мы используем язык Bru.
Для совместной работы над коллекциями API можно использовать git или любой другой контроль версий по вашему выбору.
Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269)
![bruno](/assets/images/landing-2.png) <br /><br />
### Работа на нескольких платформах 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Совместная работа через Git 👩‍💻🧑‍💻
Или другая система контроля версий по вашему выбору
![bruno](/assets/images/version-control.png) <br /><br />
### Важные ссылки 📌
- [Наше долгосрочное видение](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
- [Документация](https://docs.usebruno.com)
- [Сайт](https://www.usebruno.com)
- [Скачать Bruno](https://www.usebruno.com/downloads)
### Витрина 🎥
- [Отзывы](https://github.com/usebruno/bruno/discussions/343)
- [Центр знаний](https://github.com/usebruno/bruno/discussions/386)
- [Скриптомания](https://github.com/usebruno/bruno/discussions/385)
### Поддержка ❤️
Гав! Если вам нравится проект, нажмите на звездочку ⭐ !!!
### Поделись отзывами 📣
Если Бруно помог вам в работе и в ваших командах, пожалуйста, не забудьте поделиться своим [отзывом на нашем обсуждении в github](https://github.com/usebruno/bruno/discussions/343)
### Внести вклад 👩‍💻🧑‍💻
Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](../contributing/contributing_ru.md)
Даже если вы не можете внести свой вклад с помощью кода, пожалуйста, не стесняйтесь сообщать об ошибках и пожеланиях к функциям, которые необходимо реализовать для решения вашей задачи.
### Авторы
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### Оставайтесь на связи 🌐
[X ( Twitter )](https://twitter.com/use_bruno) <br />
[Наш сайт](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq)
### Лицензия 📄
[MIT](/license.md)

78
docs/readme/readme_tr.md Normal file
View File

@@ -0,0 +1,78 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE.
[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.
Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz.
API koleksiyonlarınız üzerinde işbirliği yapmak için git veya seçtiğiniz herhangi bir sürüm kontrolünü kullanabilirsiniz.
Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269)
![bruno](/assets/images/landing-2.png) <br /><br />
### Birden fazla platformda çalıştırın 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Git üzerinden işbirliği yapın 👩‍💻🧑‍💻
Veya seçtiğiniz herhangi bir sürüm kontrol sistemi
![bruno](/assets/images/version-control.png) <br /><br />
### Önemli Bağlantılar 📌
- [Uzun Vadeli Vizyonumuz](https://github.com/usebruno/bruno/discussions/269)
- [Yol Haritası](https://github.com/usebruno/bruno/discussions/384)
- [Dokümantasyon](https://docs.usebruno.com)
- [Web sitesi](https://www.usebruno.com)
- [İndir](https://www.usebruno.com/downloads)
### Vitrin 🎥
- [Görüşler](https://github.com/usebruno/bruno/discussions/343)
- [Bilgi Merkezi](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Destek ❤️
Woof! Projeyi beğendiyseniz, şu ⭐ düğmesine basın!
### Referansları Paylaşın 📣
Bruno işinizde ve ekiplerinizde size yardımcı olduysa, lütfen [github tartışmamızdaki referanslarınızı](https://github.com/usebruno/bruno/discussions/343) paylaşmayı unutmayın
### Katkıda Bulunun 👩‍💻🧑‍💻
Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](../contributing/contributing.md)'na göz atın
Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin.
### Katkıda Bulunanlar
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### İletişimde Kalın 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Lisans 📄
[MIT](/license.md)

78
docs/readme/readme_ua.md Normal file
View File

@@ -0,0 +1,78 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE із відкритим кодом для тестування та дослідження API
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
Bruno зберігає ваші колекції напряму у теці на вашому диску. Він використовує текстову мову розмітки Bru для збереження інформації про ваші API запити.
Ви можете використовувати git або будь-яку іншу систему контролю версій щоб спільно працювати над вашими колекціями API запитів.
Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269)
![bruno](/assets/images/landing-2.png) <br /><br />
### Кросплатформенність 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Спільна робота через Git 👩‍💻🧑‍💻
Або будь-яку іншу систему контролю версій на ваш вибір
![bruno](/assets/images/version-control.png) <br /><br />
### Важливі посилання 📌
- [Наше бачення довготривалої перспективи проекту](https://github.com/usebruno/bruno/discussions/269)
- [Дорожня карта проекту](https://github.com/usebruno/bruno/discussions/384)
- [Документація](https://docs.usebruno.com)
- [Сайт](https://www.usebruno.com)
- [Завантаження](https://www.usebruno.com/downloads)
### Вітрина 🎥
- [Відгуки](https://github.com/usebruno/bruno/discussions/343)
- [Хаб знань](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Підтримка ❤️
Гав! Якщо вам сподобався проект, тисніть на ⭐ !!
### Поділитись відгуками 📣
Якщо Bruno допоміг вам у вашій роботі і вашим командам, будь ласка не забудьте поділитись вашими [відгуками у github дискусії](https://github.com/usebruno/bruno/discussions/343)
### Зробити свій внесок 👩‍💻🧑‍💻
Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](../contributing/contributing_ua.md)
Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі.
### Автори
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### Залишайтесь на зв'язку 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[Сайт](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Ліцензія 📄
[MIT](/license.md)

28264
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,8 @@
"jest": "^29.2.0",
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"ts-jest": "^29.0.5"
"ts-jest": "^29.0.5",
"fs-extra": "^11.1.1"
},
"scripts": {
"dev:web": "npm run dev --workspace=packages/bruno-app",
@@ -31,13 +32,19 @@
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
"build:electron": "./scripts/build-electron.sh",
"build:electron": "node ./scripts/build-electron.js",
"build:electron:mac": "./scripts/build-electron.sh mac",
"build:electron:win": "./scripts/build-electron.sh win",
"build:electron:linux": "./scripts/build-electron.sh linux",
"build:electron:deb": "./scripts/build-electron.sh deb",
"build:electron:rpm": "./scripts/build-electron.sh rpm",
"build:electron:snap": "./scripts/build-electron.sh snap",
"test:e2e": "npx playwright test",
"test:report": "npx playwright show-report",
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
"prepare": "husky install"
},
"overrides": {
"rollup": "3.2.5"
},
"dependencies": {}
}
}

View File

@@ -3,11 +3,12 @@
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "cross-env ENV=dev next dev",
"dev": "cross-env ENV=dev next dev -p 3000",
"build": "next build && next export",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
},
"dependencies": {
@@ -18,7 +19,7 @@
"@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.5.0",
"@usebruno/schema": "0.6.0",
"axios": "^0.26.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
@@ -27,13 +28,17 @@
"file-dialog": "^0.0.8",
"file-saver": "^2.0.5",
"formik": "^2.2.9",
"github-markdown-css": "^5.2.0",
"graphiql": "^1.5.9",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"handlebars": "^4.7.8",
"httpsnippet": "^3.0.1",
"idb": "^7.0.0",
"immer": "^9.0.15",
"know-your-http-well": "^0.5.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it": "^13.0.2",
"mousetrap": "^1.6.5",
"nanoid": "3.3.4",
"next": "12.3.3",

View File

@@ -4,6 +4,8 @@ const StyledWrapper = styled.div`
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
line-break: anywhere;
}
.CodeMirror-overlayscroll-horizontal div,

View File

@@ -70,6 +70,35 @@ export default class CodeEditor extends React.Component {
'Ctrl-F': 'findPersistent',
Tab: function (cm) {
cm.replaceSelection(' ', 'end');
},
'Ctrl-Y': 'foldAll',
'Cmd-Y': 'foldAll',
'Ctrl-I': 'unfoldAll',
'Cmd-I': 'unfoldAll'
},
foldOptions: {
widget: (from, to) => {
var count = undefined;
var internal = this.editor.getRange(from, to);
if (this.props.mode == 'application/ld+json') {
if (this.editor.getLine(from.line).endsWith('[')) {
var toParse = '[' + internal + ']';
} else var toParse = '{' + internal + '}';
try {
count = Object.keys(JSON.parse(toParse)).length;
} catch (e) {}
} else if (this.props.mode == 'application/xml') {
var doc = new DOMParser();
try {
//add header element and remove prefix namespaces for DOMParser
var dcm = doc.parseFromString(
'<a> ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '</a>',
'application/xml'
);
count = dcm.documentElement.children.length;
} catch (e) {}
}
return count ? `\u21A4${count}\u21A6` : '\u2194';
}
}
}));
@@ -117,10 +146,14 @@ export default class CodeEditor extends React.Component {
}
render() {
if (this.editor) {
this.editor.refresh();
}
return (
<StyledWrapper
className="h-full"
className="h-full w-full"
aria-label="Code Editor"
font={this.props.font}
ref={(node) => {
this._node = node;
}}

View File

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

View File

@@ -0,0 +1,78 @@
import React, { useRef, forwardRef } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { useDispatch } from 'react-redux';
import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestAuthMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const AuthMode = ({ collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = get(collection, 'root.request.auth.mode');
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onModeChange = (value) => {
dispatch(
updateCollectionAuthMode({
collectionUid: collection.uid,
mode: value
})
);
};
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('awsv4');
}}
>
AWS Sig v4
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('none');
}}
>
No Auth
</div>
</Dropdown>
</div>
</StyledWrapper>
);
};
export default AuthMode;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,191 @@
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 AwsV4Auth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const awsv4Auth = get(collection, 'root.request.auth.awsv4', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleAccessKeyIdChange = (accessKeyId) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleSecretAccessKeyChange = (secretAccessKey) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleSessionTokenChange = (sessionToken) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleServiceChange = (service) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleRegionChange = (region) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleProfileNameChange = (profileName) => {
dispatch(
updateCollectionAuth({
mode: 'awsv4',
collectionUid: collection.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: profileName
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Access Key ID</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.accessKeyId || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAccessKeyIdChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.secretAccessKey || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSecretAccessKeyChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Session Token</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.sessionToken || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSessionTokenChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Service</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.service || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleServiceChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Region</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.region || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleRegionChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Profile Name</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.profileName || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleProfileNameChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default AwsV4Auth;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,71 @@
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 BasicAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const basicAuth = get(collection, 'root.request.auth.basic', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateCollectionAuth({
mode: 'basic',
collectionUid: collection.uid,
content: {
username: username,
password: basicAuth.password
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateCollectionAuth({
mode: 'basic',
collectionUid: collection.uid,
content: {
username: basicAuth.username,
password: password
}
})
);
};
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={basicAuth.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={basicAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BasicAuth;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,46 @@
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 BearerAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const bearerToken = get(collection, 'root.request.auth.bearer.token');
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleTokenChange = (token) => {
dispatch(
updateCollectionAuth({
mode: 'bearer',
collectionUid: collection.uid,
content: {
token: token
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Token</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={bearerToken}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleTokenChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BearerAuth;

View File

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

View File

@@ -0,0 +1,46 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import AuthMode from './AuthMode';
import AwsV4Auth from './AwsV4Auth';
import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
const dispatch = useDispatch();
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const getAuthView = () => {
switch (authMode) {
case 'awsv4': {
return <AwsV4Auth collection={collection} />;
}
case 'basic': {
return <BasicAuth collection={collection} />;
}
case 'bearer': {
return <BearerAuth collection={collection} />;
}
}
};
return (
<StyledWrapper className="w-full mt-2">
<div className="flex flex-grow justify-start items-center">
<AuthMode collection={collection} />
</div>
{getAuthView()}
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Auth;

View File

@@ -0,0 +1,43 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.settings-label {
width: 90px;
}
.certificate-icon {
color: ${(props) => props.theme.colors.text.yellow};
}
input {
width: 300px;
}
.available-certificates {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
button.remove-certificate {
color: ${(props) => props.theme.colors.text.danger};
}
}
.textbox {
border: 1px solid #ccc;
padding: 0.15rem 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,130 @@
import React from 'react';
import { IconCertificate, IconTrash, IconWorld } from '@tabler/icons';
import { useFormik } from 'formik';
import { uuid } from 'utils/common';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
const formik = useFormik({
initialValues: {
domain: '',
certFilePath: '',
keyFilePath: '',
passphrase: ''
},
validationSchema: Yup.object({
domain: Yup.string().required(),
certFilePath: Yup.string().required(),
keyFilePath: Yup.string().required(),
passphrase: Yup.string()
}),
onSubmit: (values) => {
onUpdate(values);
}
});
const getFile = (e) => {
formik.values[e.name] = e.files[0].path;
};
return (
<StyledWrapper>
<div className="flex items-center font-semibold mt-4 mb-2">
<IconCertificate className="mr-1 certificate-icon" size={24} strokeWidth={1.5} /> Client Certificates
</div>
<ul className="mt-4">
{!clientCertConfig.length
? 'None'
: clientCertConfig.map((clientCert) => (
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
</div>
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>
</div>
</li>
))}
</ul>
<h1 className="font-semibold mt-8 mb-2">Add Client Certicate</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="domain">
Domain
</label>
<input
id="domain"
type="text"
name="domain"
placeholder="*.example.org"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.domain || ''}
/>
{formik.touched.domain && formik.errors.domain ? (
<div className="ml-1 text-red-500">{formik.errors.domain}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="certFilePath">
Cert file
</label>
<input
id="certFilePath"
type="file"
name="certFilePath"
className="block"
onChange={(e) => getFile(e.target)}
/>
{formik.touched.certFilePath && formik.errors.certFilePath ? (
<div className="ml-1 text-red-500">{formik.errors.certFilePath}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="keyFilePath">
Key file
</label>
<input
id="keyFilePath"
type="file"
name="keyFilePath"
className="block"
onChange={(e) => getFile(e.target)}
/>
{formik.touched.keyFilePath && formik.errors.keyFilePath ? (
<div className="ml-1 text-red-500">{formik.errors.keyFilePath}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="passphrase">
Passphrase
</label>
<input
id="passphrase"
type="text"
name="passphrase"
className="block textbox"
onChange={formik.handleChange}
value={formik.values.passphrase || ''}
/>
{formik.touched.passphrase && formik.errors.passphrase ? (
<div className="ml-1 text-red-500">{formik.errors.passphrase}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
Add
</button>
</div>
</form>
</StyledWrapper>
);
};
export default ClientCertSettings;

View File

@@ -0,0 +1,18 @@
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};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,55 @@
import 'github-markdown-css/github-markdown.css';
import get from 'lodash/get';
import { updateCollectionDocs } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const docs = get(collection, 'root.docs', '');
const toggleViewMode = () => {
setIsEditing((prev) => !prev);
};
const onEdit = (value) => {
dispatch(
updateCollectionDocs({
collectionUid: collection.uid,
docs: value
})
);
};
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
return (
<StyledWrapper className="mt-1 h-full w-full relative">
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
{isEditing ? 'Preview' : 'Edit'}
</div>
{isEditing ? (
<CodeEditor
collection={collection}
theme={storedTheme}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}
mode="application/text"
/>
) : (
<Markdown onDoubleClick={toggleViewMode} content={docs} />
)}
</StyledWrapper>
);
};
export default Docs;

View File

@@ -0,0 +1,56 @@
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(3) {
width: 70px;
}
}
}
.btn-add-header {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='checkbox'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@@ -0,0 +1,151 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import {
addCollectionHeader,
updateCollectionHeader,
deleteCollectionHeader
} from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const Headers = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const headers = get(collection, 'root.request.headers', []);
const addHeader = () => {
dispatch(
addCollectionHeader({
collectionUid: collection.uid
})
);
};
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleHeaderValueChange = (e, _header, type) => {
const header = cloneDeep(_header);
switch (type) {
case 'name': {
header.name = e.target.value;
break;
}
case 'value': {
header.value = e.target.value;
break;
}
case 'enabled': {
header.enabled = e.target.checked;
break;
}
}
dispatch(
updateCollectionHeader({
header: header,
collectionUid: collection.uid
})
);
};
const handleRemoveHeader = (header) => {
dispatch(
deleteCollectionHeader({
headerUid: header.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{headers && headers.length
? headers.map((header) => {
return (
<tr key={header.uid}>
<td>
<SingleLineEditor
value={header.name}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'name'
)
}
autocomplete={headerAutoCompleteList}
collection={collection}
/>
</td>
<td>
<SingleLineEditor
value={header.value}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'value'
)
}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={header.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
+ Add Header
</button>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Headers;

View File

@@ -1,13 +1,55 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
const ProxySettings = ({ proxyConfig, onUpdate }) => {
const proxySchema = Yup.object({
enabled: Yup.string().oneOf(['global', 'true', 'false']),
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string()
.when('enabled', {
is: true,
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable()
})
.max(1024),
port: Yup.number()
.when('enabled', {
is: true,
then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'),
otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null))
})
.min(1)
.max(65535),
auth: Yup.object()
.when('enabled', {
is: true,
then: Yup.object({
enabled: Yup.boolean(),
username: Yup.string()
.when('enabled', {
is: true,
then: (username) => username.required('Specify username for proxy authentication.')
})
.max(1024),
password: Yup.string()
.when('enabled', {
is: true,
then: (password) => password.required('Specify password for proxy authentication.')
})
.max(1024)
})
})
.optional(),
bypassProxy: Yup.string().optional().max(1024)
});
const formik = useFormik({
initialValues: {
enabled: proxyConfig.enabled || false,
enabled: proxyConfig.enabled || 'global',
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
@@ -15,27 +57,33 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
},
bypassProxy: proxyConfig.bypassProxy || ''
},
validationSchema: Yup.object({
enabled: Yup.boolean(),
protocol: Yup.string().oneOf(['http', 'https']),
hostname: Yup.string().max(1024),
port: Yup.number().min(0).max(65535),
auth: Yup.object({
enabled: Yup.boolean(),
username: Yup.string().max(1024),
password: Yup.string().max(1024)
})
}),
validationSchema: proxySchema,
onSubmit: (values) => {
onUpdate(values);
proxySchema
.validate(values, { abortEarly: true })
.then((validatedProxy) => {
// serialize 'enabled' to boolean
if (validatedProxy.enabled === 'true') {
validatedProxy.enabled = true;
} else if (validatedProxy.enabled === 'false') {
validatedProxy.enabled = false;
}
onUpdate(validatedProxy);
})
.catch((error) => {
let errMsg = error.message || 'Preferences validation error';
toast.error(errMsg);
});
}
});
useEffect(() => {
formik.setValues({
enabled: proxyConfig.enabled || false,
enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global',
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
@@ -43,7 +91,8 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
},
bypassProxy: proxyConfig.bypassProxy || ''
});
}, [proxyConfig]);
@@ -51,18 +100,64 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
<StyledWrapper>
<h1 className="font-medium mb-3">Proxy Settings</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="ml-4 mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
Config
<Tooltip
text={`
<div>
<ul>
<li><span style="width: 50px;display:inline-block;">global</span> - use global proxy config</li>
<li><span style="width: 50px;display:inline-block;">enabled</span> - use collection proxy config</li>
<li><span style="width: 50px;display:inline-block;">disable</span> - disable proxy</li>
</ul>
</div>
`}
tooltipId="request-var"
/>
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="enabled"
value="global"
checked={formik.values.enabled === 'global'}
onChange={formik.handleChange}
className="mr-1"
/>
global
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="enabled"
value={'true'}
checked={formik.values.enabled === 'true'}
onChange={formik.handleChange}
className="mr-1"
/>
enabled
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="enabled"
value={'false'}
checked={formik.values.enabled === 'false'}
onChange={formik.handleChange}
className="mr-1"
/>
disabled
</label>
</div>
</div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center mr-4">
<label className="flex items-center">
<input
type="radio"
name="protocol"
@@ -73,7 +168,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
/>
http
</label>
<label className="flex items-center">
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
@@ -84,9 +179,31 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
/>
https
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
socks4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
socks5
</label>
</div>
</div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
@@ -103,10 +220,10 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="text-red-500">{formik.errors.hostname}</div>
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
@@ -122,9 +239,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
@@ -136,7 +255,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
/>
</div>
<div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
@@ -153,10 +272,10 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="text-red-500">{formik.errors.auth.username}</div>
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
<div className="ml-4 mb-3 flex items-center">
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
@@ -173,12 +292,32 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
onChange={formik.handleChange}
/>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="text-red-500">{formik.errors.auth.password}</div>
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-md btn-secondary">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save
</button>
</div>

View File

@@ -0,0 +1,13 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
height: inherit;
}
div.title {
color: var(--color-tab-inactive);
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,73 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Script = ({ collection }) => {
const dispatch = useDispatch();
const requestScript = get(collection, 'root.request.script.req', '');
const responseScript = get(collection, 'root.request.script.res', '');
const { storedTheme } = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateCollectionRequestScript({
script: value,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateCollectionResponseScript({
script: value,
collectionUid: collection.uid
})
);
};
const handleSave = () => {
dispatch(saveCollectionRoot(collection.uid));
};
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor
collection={collection}
value={requestScript || ''}
theme={storedTheme}
onEdit={onRequestScriptEdit}
mode="javascript"
onSave={handleSave}
/>
</div>
<div className="flex-1 mt-6">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<CodeEditor
collection={collection}
value={responseScript || ''}
theme={storedTheme}
onEdit={onResponseScriptEdit}
mode="javascript"
onSave={handleSave}
/>
</div>
<div className="mt-12">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Script;

View File

@@ -1,6 +1,32 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
div.tabs {
div.tab {
padding: 6px 0px;
border: none;
border-bottom: solid 2px transparent;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important;
box-shadow: none !important;
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
}
}
table {
thead,
td {

View File

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

View File

@@ -0,0 +1,47 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateCollectionTests } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Tests = ({ collection }) => {
const dispatch = useDispatch();
const tests = get(collection, 'root.request.tests', '');
const { storedTheme } = useTheme();
const onEdit = (value) => {
dispatch(
updateCollectionTests({
tests: value,
collectionUid: collection.uid
})
);
};
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
return (
<StyledWrapper className="w-full flex flex-col h-full">
<CodeEditor
collection={collection}
value={tests || ''}
theme={storedTheme}
onEdit={onEdit}
mode="javascript"
onSave={handleSave}
/>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Tests;

View File

@@ -1,20 +1,56 @@
import React from 'react';
import classnames from 'classnames';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import toast from 'react-hot-toast';
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux';
import ProxySettings from './ProxySettings';
import ClientCertSettings from './ClientCertSettings';
import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
import Test from './Tests';
import Docs from './Docs';
import StyledWrapper from './StyledWrapper';
const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch();
const tab = collection.settingsSelectedTab;
const setTab = (tab) => {
dispatch(
updateSettingsSelectedTab({
collectionUid: collection.uid,
tab
})
);
};
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []);
const onProxySettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.proxy = config;
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully.');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
const onClientCertSettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
if (!brunoConfig.clientCertificates) {
brunoConfig.clientCertificates = {
enabled: true,
certs: [config]
};
} else {
brunoConfig.clientCertificates.certs.push(config);
}
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
@@ -22,11 +58,84 @@ const CollectionSettings = ({ collection }) => {
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
return (
<StyledWrapper className="px-4 py-4">
<h1 className="font-semibold mb-4">Collection Settings</h1>
const onClientCertSettingsRemove = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.clientCertificates.certs = brunoConfig.clientCertificates.certs.filter(
(item) => item.domain != config.domain
);
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
<ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />
const getTabPanel = (tab) => {
switch (tab) {
case 'headers': {
return <Headers collection={collection} />;
}
case 'auth': {
return <Auth collection={collection} />;
}
case 'script': {
return <Script collection={collection} />;
}
case 'tests': {
return <Test collection={collection} />;
}
case 'proxy': {
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
}
case 'clientCert': {
return (
<ClientCertSettings
clientCertConfig={clientCertConfig}
onUpdate={onClientCertSettingsUpdate}
onRemove={onClientCertSettingsRemove}
/>
);
}
case 'docs': {
return <Docs collection={collection} />;
}
}
};
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === tab
});
};
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('headers')} role="tab" onClick={() => setTab('headers')}>
Headers
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
Tests
</div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
</div>
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
Client Certificates
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs
</div>
</div>
<section className={`flex ${['auth', 'script', 'docs', 'clientCert'].includes(tab) ? '' : 'mt-4'}`}>
{getTabPanel(tab)}
</section>
</StyledWrapper>
);
};

View File

@@ -0,0 +1,18 @@
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};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,60 @@
import 'github-markdown-css/github-markdown.css';
import get from 'lodash/get';
import { updateRequestDocs } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme/index';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
const Documentation = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs');
const toggleViewMode = () => {
setIsEditing((prev) => !prev);
};
const onEdit = (value) => {
dispatch(
updateRequestDocs({
itemUid: item.uid,
collectionUid: collection.uid,
docs: value
})
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
if (!item) {
return null;
}
return (
<StyledWrapper className="mt-1 h-full w-full relative">
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
{isEditing ? 'Preview' : 'Edit'}
</div>
{isEditing ? (
<CodeEditor
collection={collection}
theme={storedTheme}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}
mode="application/text"
/>
) : (
<Markdown onDoubleClick={toggleViewMode} content={docs} />
)}
</StyledWrapper>
);
};
export default Documentation;

View File

@@ -14,6 +14,8 @@ const Wrapper = styled.div`
background-color: ${(props) => props.theme.dropdown.bg};
box-shadow: ${(props) => props.theme.dropdown.shadow};
border-radius: 3px;
max-height: 90vh;
overflow-y: auto;
.tippy-content {
padding-left: 0;

View File

@@ -0,0 +1,75 @@
import Modal from 'components/Modal/index';
import Portal from 'components/Portal/index';
import { useFormik } from 'formik';
import { copyEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { useEffect, useRef } from 'react';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import * as Yup from 'yup';
const CopyEnvironment = ({ collection, environment, onClose }) => {
const dispatch = useDispatch();
const inputRef = useRef();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
name: environment.name + ' - Copy'
},
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')
}),
onSubmit: (values) => {
dispatch(copyEnvironment(values.name, environment.uid, collection.uid))
.then(() => {
toast.success('Environment created in collection');
onClose();
})
.catch(() => toast.error('An error occurred while created the environment'));
}
});
useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.focus();
}
}, [inputRef]);
const onSubmit = () => {
formik.handleSubmit();
};
return (
<Portal>
<Modal size="sm" title={'Copy Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name" className="block font-semibold">
New Environment Name
</label>
<input
id="environment-name"
type="text"
name="name"
ref={inputRef}
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>
</Portal>
);
};
export default CopyEnvironment;

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import Portal from 'components/Portal/index';
import Modal from 'components/Modal/index';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
import toast from 'react-hot-toast';
import { useFormik } from 'formik';
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';
@@ -17,7 +17,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be atleast 1 characters')
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),

View File

@@ -13,10 +13,12 @@ const Wrapper = styled.div`
padding: 4px 10px;
&:nth-child(1),
&:nth-child(4),
&:nth-child(5) {
&:nth-child(4) {
width: 70px;
}
&:nth-child(5) {
width: 40px;
}
&:nth-child(2) {
width: 25%;

View File

@@ -1,68 +1,79 @@
import React, { useReducer } from 'react';
import React from 'react';
import toast from 'react-hot-toast';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import reducer from './reducer';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { uuid } from 'utils/common';
const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { variables, hasChanges } = state;
const saveChanges = () => {
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
.then(() => {
toast.success('Changes saved successfully');
reducerDispatch({
type: 'CHANGES_SAVED'
});
const formik = useFormik({
enableReinitialize: true,
initialValues: environment.variables || [],
validationSchema: Yup.array().of(
Yup.object({
enabled: Yup.boolean(),
name: Yup.string()
.required('Name cannot be empty')
.matches(/^(?!\d)\w*$/, 'Name contains invalid characters')
.trim(),
secret: Yup.boolean(),
type: Yup.string(),
uid: Yup.string(),
value: Yup.string().trim()
})
.catch(() => toast.error('An error occurred while saving the changes'));
),
onSubmit: (values) => {
if (!formik.dirty) {
toast.error('Nothing to save');
return;
}
dispatch(saveEnvironment(cloneDeep(values), environment.uid, collection.uid))
.then(() => {
toast.success('Changes saved successfully');
formik.resetForm({ values });
})
.catch(() => toast.error('An error occurred while saving the changes'));
}
});
const ErrorMessage = ({ name }) => {
const meta = formik.getFieldMeta(name);
console.log(name, meta);
if (!meta.error) {
return null;
}
return (
<label htmlFor={name} className="text-red-500">
{meta.error}
</label>
);
};
const addVariable = () => {
reducerDispatch({
type: 'ADD_VAR'
});
const newVariable = {
uid: uuid(),
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
};
formik.setFieldValue(formik.values.length, newVariable, false);
};
const handleVarChange = (e, _variable, type) => {
const variable = cloneDeep(_variable);
switch (type) {
case 'name': {
variable.name = e.target.value;
break;
}
case 'value': {
variable.value = e.target.value;
break;
}
case 'enabled': {
variable.enabled = e.target.checked;
break;
}
case 'secret': {
variable.secret = e.target.checked;
break;
}
}
reducerDispatch({
type: 'UPDATE_VAR',
variable
});
};
const handleRemoveVars = (variable) => {
reducerDispatch({
type: 'DELETE_VAR',
variable
});
const handleRemoveVar = (id) => {
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
};
return (
@@ -78,55 +89,57 @@ const EnvironmentVariables = ({ environment, collection }) => {
</tr>
</thead>
<tbody>
{variables && variables.length
? variables.map((variable, index) => {
return (
<tr key={variable.uid}>
<td className="text-center">
<input
type="checkbox"
checked={variable.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'enabled')}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={variable.name}
className="mousetrap"
onChange={(e) => handleVarChange(e, variable, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={variable.value}
theme={storedTheme}
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
collection={collection}
/>
</td>
<td>
<input
type="checkbox"
checked={variable.secret}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'secret')}
/>
</td>
<td>
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</td>
</tr>
);
})
: null}
{formik.values.map((variable, index) => (
<tr key={variable.uid}>
<td className="text-center">
<input
type="checkbox"
className="mr-3 mousetrap"
name={`${index}.enabled`}
checked={variable.enabled}
onChange={formik.handleChange}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
className="mousetrap"
id={`${index}.name`}
name={`${index}.name`}
value={formik.values[index].name}
onChange={formik.handleChange}
/>
<ErrorMessage name={`${index}.name`} />
</td>
<td>
<SingleLineEditor
theme={storedTheme}
collection={collection}
name={`${index}.value`}
value={variable.value}
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
/>
</td>
<td>
<input
type="checkbox"
className="mr-3 mousetrap"
name={`${index}.secret`}
checked={variable.secret}
onChange={formik.handleChange}
/>
</td>
<td>
<button onClick={() => handleRemoveVar(variable.uid)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</td>
</tr>
))}
</tbody>
</table>
@@ -137,12 +150,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
</div>
<div>
<button
type="submit"
className="submit btn btn-md btn-secondary mt-2"
disabled={!hasChanges}
onClick={saveChanges}
>
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
Save
</button>
</div>

View File

@@ -1,52 +0,0 @@
import produce from 'immer';
import find from 'lodash/find';
import filter from 'lodash/filter';
import { uuid } from 'utils/common';
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_VAR': {
return produce(state, (draft) => {
draft.variables.push({
uid: uuid(),
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
});
draft.hasChanges = true;
});
}
case 'UPDATE_VAR': {
return produce(state, (draft) => {
const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
variable.name = action.variable.name;
variable.value = action.variable.value;
variable.enabled = action.variable.enabled;
variable.secret = action.variable.secret;
draft.hasChanges = true;
});
}
case 'DELETE_VAR': {
return produce(state, (draft) => {
draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid);
draft.hasChanges = true;
});
}
case 'CHANGES_SAVED': {
return produce(state, (draft) => {
draft.hasChanges = false;
});
}
default: {
return state;
}
}
};
export default reducer;

View File

@@ -1,12 +1,14 @@
import React, { useState } from 'react';
import { IconEdit, IconTrash, IconDatabase } from '@tabler/icons';
import EnvironmentVariables from './EnvironmentVariables';
import RenameEnvironment from '../../RenameEnvironment';
import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
import { useState } from 'react';
import CopyEnvironment from '../../CopyEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
import RenameEnvironment from '../../RenameEnvironment';
import EnvironmentVariables from './EnvironmentVariables';
const EnvironmentDetails = ({ environment, collection }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [openCopyModal, setOpenCopyModal] = useState(false);
return (
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
@@ -20,6 +22,9 @@ const EnvironmentDetails = ({ environment, collection }) => {
collection={collection}
/>
)}
{openCopyModal && (
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} collection={collection} />
)}
<div className="flex">
<div className="flex flex-grow items-center">
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
@@ -27,6 +32,7 @@ const EnvironmentDetails = ({ environment, collection }) => {
</div>
<div className="flex gap-x-4 pl-4">
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
<IconCopy className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenCopyModal(true)} />
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
</div>
</div>

View File

@@ -10,6 +10,9 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
min-height: 400px;
height: 100%;
max-height: 85vh;
overflow-y: auto;
}
.environment-item {
@@ -35,18 +38,21 @@ const StyledWrapper = styled.div`
}
}
.btn-create-environment {
.btn-create-environment,
.btn-import-environment {
padding: 8px 10px;
cursor: pointer;
border-bottom: none;
color: ${(props) => props.theme.textLink};
&:hover {
span {
text-decoration: underline;
}
span:hover {
text-decoration: underline;
}
}
.btn-import-environment {
color: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

View File

@@ -2,13 +2,18 @@ import React, { useEffect, useState, forwardRef, useRef } from 'react';
import { findEnvironmentInCollection } from 'utils/collections';
import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from '../CreateEnvironment/index';
import CreateEnvironment from '../CreateEnvironment';
import { IconDownload, IconShieldLock } from '@tabler/icons';
import ImportEnvironment from '../ImportEnvironment';
import ManageSecrets from '../ManageSecrets';
import StyledWrapper from './StyledWrapper';
const EnvironmentList = ({ collection }) => {
const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
@@ -48,9 +53,11 @@ const EnvironmentList = ({ collection }) => {
return (
<StyledWrapper>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
<div className="flex">
<div>
<div className="environments-sidebar">
<div className="environments-sidebar flex flex-col">
{environments &&
environments.length &&
environments.map((env) => (
@@ -65,6 +72,17 @@ const EnvironmentList = ({ collection }) => {
<div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}>
+ <span>Create</span>
</div>
<div className="mt-auto btn-import-environment">
<div className="flex items-center" onClick={() => setOpenImportModal(true)}>
<IconDownload size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Import</span>
</div>
<div className="flex items-center mt-2" onClick={() => setOpenManageSecretsModal(true)}>
<IconShieldLock size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Managing Secrets</span>
</div>
</div>
</div>
</div>
<EnvironmentDetails environment={selectedEnvironment} collection={collection} />

View File

@@ -0,0 +1,39 @@
import React from 'react';
import Portal from 'components/Portal';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import importPostmanEnvironment from 'utils/importers/postman-environment';
import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { toastError } from 'utils/common/error';
import Modal from 'components/Modal';
const ImportEnvironment = ({ onClose, collection }) => {
const dispatch = useDispatch();
const handleImportPostmanEnvironment = () => {
importPostmanEnvironment()
.then((environment) => {
dispatch(importEnvironment(environment.name, environment.variables, collection.uid))
.then(() => {
toast.success('Environment imported successfully');
onClose();
})
.catch(() => toast.error('An error occurred while importing the environment'));
})
.catch((err) => toastError(err, 'Postman Import environment failed'));
};
return (
<Portal>
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<div className="text-link hover:underline cursor-pointer" onClick={handleImportPostmanEnvironment}>
Postman Environment
</div>
</div>
</Modal>
</Portal>
);
};
export default ImportEnvironment;

View File

@@ -0,0 +1,31 @@
import React from 'react';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
const ManageSecrets = ({ onClose }) => {
return (
<Portal>
<Modal size="sm" title="Manage Secrets" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<p>In any collection, there are secrets that need to be managed.</p>
<p className="mt-2">These secrets can be anything such as API keys, passwords, or tokens.</p>
<p className="mt-4">Bruno offers two approaches to manage secrets in collections.</p>
<p className="mt-2">
Read more about it in our{' '}
<a
href="https://docs.usebruno.com/secrets-management/overview.html"
target="_blank"
rel="noreferrer"
className="text-link hover:underline"
>
docs
</a>
.
</p>
</div>
</Modal>
</Portal>
);
};
export default ManageSecrets;

View File

@@ -17,7 +17,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be atleast 1 characters')
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),

View File

@@ -3,10 +3,12 @@ import React, { useState } from 'react';
import CreateEnvironment from './CreateEnvironment';
import EnvironmentList from './EnvironmentList';
import StyledWrapper from './StyledWrapper';
import ImportEnvironment from './ImportEnvironment';
const EnvironmentSettings = ({ collection, onClose }) => {
const { environments } = collection;
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
if (!environments || !environments.length) {
return (
@@ -20,13 +22,23 @@ const EnvironmentSettings = ({ collection, onClose }) => {
hideCancel={true}
>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
<div className="text-center">
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
<div className="text-center flex flex-col">
<p>No environments found!</p>
<button
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
onClick={() => setOpenCreateModal(true)}
>
+ <span>Create Environment</span>
<span>Create Environment</span>
</button>
<span>Or</span>
<button
className="btn-import-environment text-link pl-2 pr-2 py-3 select-none"
onClick={() => setOpenImportModal(true)}
>
<span>Import Environment</span>
</button>
</div>
</Modal>

View File

@@ -0,0 +1,83 @@
import styled from 'styled-components';
const StyledMarkdownBodyWrapper = styled.div`
background: transparent;
height: inherit;
.markdown-body {
background: transparent;
overflow-y: auto;
color: ${(props) => props.theme.text};
box-sizing: border-box;
height: 100%;
margin: 0 auto;
padding-top: 0.5rem;
font-size: 0.875rem;
h1 {
margin: 0.67em 0;
font-weight: var(--base-text-weight-semibold, 600);
padding-bottom: 0.3em;
font-size: 1.3;
border-bottom: 1px solid var(--color-border-muted);
}
h2 {
font-weight: var(--base-text-weight-semibold, 600);
padding-bottom: 0.3em;
font-size: 1.2;
border-bottom: 1px solid var(--color-border-muted);
}
h3 {
font-weight: var(--base-text-weight-semibold, 600);
font-size: 1.1em;
}
h4 {
font-weight: var(--base-text-weight-semibold, 600);
font-size: 1em;
}
h5 {
font-weight: var(--base-text-weight-semibold, 600);
font-size: 0.95em;
}
h6 {
font-weight: var(--base-text-weight-semibold, 600);
font-size: 0.9em;
color: var(--color-fg-muted);
}
hr {
box-sizing: content-box;
overflow: hidden;
border-bottom: 1px solid var(--color-border-muted);
height: 1px;
padding: 0;
margin: 24px 0;
background-color: var(--color-border-default);
border: 0;
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
pre {
background: ${(props) => props.theme.sidebar.bg};
}
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
`;
export default StyledMarkdownBodyWrapper;

View File

@@ -0,0 +1,32 @@
import MarkdownIt from 'markdown-it';
import StyledWrapper from './StyledWrapper';
const md = new MarkdownIt();
const Markdown = ({ onDoubleClick, content }) => {
const handleOnDoubleClick = (event) => {
switch (event.detail) {
case 2: {
onDoubleClick();
break;
}
case 1:
default: {
break;
}
}
};
const htmlFromMarkdown = md.render(content || '');
return (
<StyledWrapper>
<div
className="markdown-body"
dangerouslySetInnerHTML={{ __html: htmlFromMarkdown }}
onClick={handleOnDoubleClick}
/>
</StyledWrapper>
);
};
export default Markdown;

View File

@@ -1,6 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
color: ${(props) => props.theme.text};
&.modal--animate-out {
animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
@@ -20,6 +22,7 @@ const Wrapper = styled.div`
justify-content: center;
overflow-y: auto;
z-index: 10;
background-color: rgba(0, 0, 0, 0.5);
}
.bruno-modal-card {

View File

@@ -61,19 +61,20 @@ const Modal = ({
children,
confirmDisabled,
hideCancel,
hideFooter
hideFooter,
closeModalFadeTimeout = 500
}) => {
const [isClosing, setIsClosing] = useState(false);
const escFunction = (event) => {
const escKeyCode = 27;
if (event.keyCode === escKeyCode) {
closeModal();
closeModal({ type: 'esc' });
}
};
const closeModal = () => {
const closeModal = (args) => {
setIsClosing(true);
setTimeout(() => handleCancel(), 500);
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
};
useEffect(() => {
@@ -94,12 +95,12 @@ const Modal = ({
return (
<StyledWrapper className={classes}>
<div className={`bruno-modal-card modal-${size}`}>
<ModalHeader title={title} handleCancel={() => closeModal()} />
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} />
<ModalContent>{children}</ModalContent>
<ModalFooter
confirmText={confirmText}
cancelText={cancelText}
handleCancel={() => closeModal()}
handleCancel={() => closeModal({ type: 'button' })}
handleSubmit={handleConfirm}
confirmDisabled={confirmDisabled}
hideCancel={hideCancel}
@@ -108,7 +109,12 @@ const Modal = ({
</div>
{/* Clicking on backdrop closes the modal */}
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
<div
className="bruno-modal-backdrop"
onClick={() => {
closeModal({ type: 'backdrop' });
}}
/>
</StyledWrapper>
);
};

View File

@@ -0,0 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
`;
export default StyledWrapper;

View File

@@ -0,0 +1,53 @@
import React, { useState } from 'react';
import get from 'lodash/get';
import { useSelector, useDispatch } from 'react-redux';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
const Font = ({ close }) => {
const dispatch = useDispatch();
const preferences = useSelector((state) => state.app.preferences);
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
const handleInputChange = (event) => {
setCodeFont(event.target.value);
};
const handleSave = () => {
dispatch(
savePreferences({
...preferences,
font: {
codeFont
}
})
).then(() => {
close();
});
};
return (
<StyledWrapper>
<label className="block font-medium">Code Editor Font</label>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Font;

View File

@@ -1,36 +1,103 @@
import React, { useState } from 'react';
import { usePreferences } from 'providers/Preferences';
import React from 'react';
import { useFormik } from 'formik';
import { useSelector, useDispatch } from 'react-redux';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
const General = () => {
const { preferences, setPreferences } = usePreferences();
const General = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const dispatch = useDispatch();
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
const handleCheckboxChange = () => {
const updatedPreferences = {
...preferences,
request: {
...preferences.request,
sslVerification: !sslVerification
}
};
setPreferences(updatedPreferences)
.then(() => {
setSslVerification(!sslVerification);
const preferencesSchema = Yup.object().shape({
sslVerification: Yup.boolean(),
timeout: Yup.mixed()
.transform((value, originalValue) => {
return originalValue === '' ? undefined : value;
})
.catch((err) => {
console.error(err);
});
.nullable()
.test('isNumber', 'Request Timeout must be a number', (value) => {
return value === undefined || !isNaN(value);
})
.test('isValidTimeout', 'Request Timeout must be equal or greater than 0', (value) => {
return value === undefined || Number(value) >= 0;
})
});
const formik = useFormik({
initialValues: {
sslVerification: preferences.request.sslVerification,
timeout: preferences.request.timeout
},
validationSchema: preferencesSchema,
onSubmit: async (values) => {
try {
const newPreferences = await preferencesSchema.validate(values, { abortEarly: true });
handleSave(newPreferences);
} catch (error) {
console.error('Preferences validation error:', error.message);
}
}
});
const handleSave = (newPreferences) => {
dispatch(
savePreferences({
...preferences,
request: {
sslVerification: newPreferences.sslVerification,
timeout: newPreferences.timeout
}
})
)
.then(() => {
close();
})
.catch((err) => console.log(err) && toast.error('Failed to update preferences'));
};
return (
<StyledWrapper>
<div className="flex items-center mt-2">
<input type="checkbox" checked={sslVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" />
SSL Certificate Verification
</div>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="flex items-center mt-2">
<label className="block font-medium mr-2 select-none" style={{ minWidth: 200 }} htmlFor="sslVerification">
SSL/TLS Certificate Verification
</label>
<input
id="ssl-cert-verification"
type="checkbox"
name="sslVerification"
checked={formik.values.sslVerification}
onChange={formik.handleChange}
className="mousetrap mr-0"
/>
</div>
<div className="flex flex-col mt-6">
<label className="block font-medium select-none" htmlFor="timeout">
Request Timeout (in ms)
</label>
<input
type="text"
name="timeout"
className="block textbox mt-2 w-16"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.timeout}
/>
</div>
{formik.touched.timeout && formik.errors.timeout ? (
<div className="text-red-500">{formik.errors.timeout}</div>
) : null}
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save
</button>
</div>
</form>
</StyledWrapper>
);
};

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.settings-label {
width: 80px;
}
.textbox {
border: 1px solid #ccc;
padding: 0.15rem 0.45rem;
box-shadow: none;
outline: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,292 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
import { useDispatch, useSelector } from 'react-redux';
const ProxySettings = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const dispatch = useDispatch();
const proxySchema = Yup.object({
enabled: Yup.boolean(),
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string()
.when('enabled', {
is: true,
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable()
})
.max(1024),
port: Yup.number()
.when('enabled', {
is: true,
then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'),
otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null))
})
.min(1)
.max(65535),
auth: Yup.object()
.when('enabled', {
is: true,
then: Yup.object({
enabled: Yup.boolean(),
username: Yup.string()
.when(['enabled'], {
is: true,
then: (username) => username.required('Specify username for proxy authentication.')
})
.max(1024),
password: Yup.string()
.when('enabled', {
is: true,
then: (password) => password.required('Specify password for proxy authentication.')
})
.max(1024)
})
})
.optional(),
bypassProxy: Yup.string().optional().max(1024)
});
const formik = useFormik({
initialValues: {
enabled: preferences.proxy.enabled || false,
protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || 0,
auth: {
enabled: preferences.proxy.auth ? preferences.proxy.auth.enabled || false : false,
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
},
bypassProxy: preferences.proxy.bypassProxy || ''
},
validationSchema: proxySchema,
onSubmit: (values) => {
onUpdate(values);
}
});
const onUpdate = (values) => {
proxySchema
.validate(values, { abortEarly: true })
.then((validatedProxy) => {
dispatch(
savePreferences({
...preferences,
proxy: validatedProxy
})
).then(() => {
close();
});
})
.catch((error) => {
let errMsg = error.message || 'Preferences validation error';
toast.error(errMsg);
});
};
useEffect(() => {
formik.setValues({
enabled: preferences.proxy.enabled || false,
protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || '',
auth: {
enabled: preferences.proxy.auth ? preferences.proxy.auth.enabled || false : false,
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
},
bypassProxy: preferences.proxy.bypassProxy || ''
});
}, [preferences]);
return (
<StyledWrapper>
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
http
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
https
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
socks4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
socks5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<input
id="auth.password"
type="text"
name="auth.password"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-md btn-secondary">
Save
</button>
</div>
</form>
</StyledWrapper>
);
};
export default ProxySettings;

View File

@@ -3,7 +3,9 @@ import classnames from 'classnames';
import React, { useState } from 'react';
import Support from './Support';
import General from './General';
import Font from './Font';
import Theme from './Theme';
import Proxy from './ProxySettings';
import StyledWrapper from './StyledWrapper';
const Preferences = ({ onClose }) => {
@@ -18,16 +20,24 @@ const Preferences = ({ onClose }) => {
const getTabPanel = (tab) => {
switch (tab) {
case 'general': {
return <General />;
return <General close={onClose} />;
}
case 'proxy': {
return <Proxy close={onClose} />;
}
case 'theme': {
return <Theme />;
return <Theme close={onClose} />;
}
case 'support': {
return <Support />;
}
case 'font': {
return <Font close={onClose} />;
}
}
};
@@ -41,6 +51,12 @@ const Preferences = ({ onClose }) => {
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
Theme
</div>
<div className={getTabClassname('font')} role="tab" onClick={() => setTab('font')}>
Font
</div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
</div>
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
Support
</div>

View File

@@ -1,4 +1,7 @@
import React from 'react';
import { useTheme } from 'providers/Theme/index';
import darkTheme from 'themes/dark';
import lightTheme from 'themes/light';
/**
* Assertion operators
@@ -76,10 +79,16 @@ const AssertionOperator = ({ operator, onChange }) => {
}
};
const { storedTheme } = useTheme();
return (
<select value={operator} onChange={handleChange} className="mousetrap">
{operators.map((operator) => (
<option key={operator} value={operator}>
<option
style={{ backgroundColor: storedTheme === 'dark' ? darkTheme.bg : lightTheme.bg }}
key={operator}
value={operator}
>
{getLabel(operator)}
</option>
))}

View File

@@ -178,7 +178,7 @@ const AssertionRow = ({
handleAssertionChange(
{
target: {
value: newValue
value: `${operator} ${newValue}`
}
},
assertion,
@@ -197,10 +197,11 @@ const AssertionRow = ({
<input
type="checkbox"
checked={assertion.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleAssertionChange(e, assertion, 'enabled')}
/>
<button onClick={() => handleRemoveAssertion(assertion)}>
<button tabIndex="-1" onClick={() => handleRemoveAssertion(assertion)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

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

View File

@@ -0,0 +1,79 @@
import React, { useRef, forwardRef } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { useDispatch } from 'react-redux';
import { updateRequestAuthMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestAuthMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const AuthMode = ({ item, collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onModeChange = (value) => {
dispatch(
updateRequestAuthMode({
itemUid: item.uid,
collectionUid: collection.uid,
mode: value
})
);
};
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('awsv4');
}}
>
AWS Sig v4
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('none');
}}
>
No Auth
</div>
</Dropdown>
</div>
</StyledWrapper>
);
};
export default AuthMode;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,205 @@
import React, { useState } 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';
import { update } from 'lodash';
const AwsV4Auth = ({ onTokenChange, item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {});
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleAccessKeyIdChange = (accessKeyId) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleSecretAccessKeyChange = (secretAccessKey) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleSessionTokenChange = (sessionToken) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleServiceChange = (service) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: service,
region: awsv4Auth.region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleRegionChange = (region) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: region,
profileName: awsv4Auth.profileName
}
})
);
};
const handleProfileNameChange = (profileName) => {
dispatch(
updateAuth({
mode: 'awsv4',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
accessKeyId: awsv4Auth.accessKeyId,
secretAccessKey: awsv4Auth.secretAccessKey,
sessionToken: awsv4Auth.sessionToken,
service: awsv4Auth.service,
region: awsv4Auth.region,
profileName: profileName
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Access Key ID</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.accessKeyId || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleAccessKeyIdChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Secret Access Key</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.secretAccessKey || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSecretAccessKeyChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Session Token</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.sessionToken || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleSessionTokenChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Service</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.service || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleServiceChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Region</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.region || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleRegionChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Profile Name</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={awsv4Auth.profileName || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleProfileNameChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default AwsV4Auth;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,76 @@
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 BasicAuth = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const basicAuth = item.draft ? get(item, 'draft.request.auth.basic', {}) : get(item, 'request.auth.basic', {});
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateAuth({
mode: 'basic',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: username,
password: basicAuth.password
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateAuth({
mode: 'basic',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: basicAuth.username,
password: password
}
})
);
};
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={basicAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={basicAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BasicAuth;

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
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,51 @@
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 BearerAuth = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const bearerToken = item.draft
? get(item, 'draft.request.auth.bearer.token')
: get(item, 'request.auth.bearer.token');
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleTokenChange = (token) => {
dispatch(
updateAuth({
mode: 'bearer',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
token: token
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Token</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={bearerToken}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleTokenChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BearerAuth;

View File

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

View File

@@ -0,0 +1,35 @@
import React from 'react';
import get from 'lodash/get';
import AuthMode from './AuthMode';
import AwsV4Auth from './AwsV4Auth';
import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import StyledWrapper from './StyledWrapper';
const Auth = ({ item, collection }) => {
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
const getAuthView = () => {
switch (authMode) {
case 'awsv4': {
return <AwsV4Auth collection={collection} item={item} />;
}
case 'basic': {
return <BasicAuth collection={collection} item={item} />;
}
case 'bearer': {
return <BearerAuth collection={collection} item={item} />;
}
}
};
return (
<StyledWrapper className="w-full mt-1">
<div className="flex flex-grow justify-start items-center">
<AuthMode item={item} collection={collection} />
</div>
{getAuthView()}
</StyledWrapper>
);
};
export default Auth;

View File

@@ -107,6 +107,7 @@ const FormUrlEncodedParams = ({ item, collection }) => {
'value'
)
}
allowNewlines={true}
onRun={handleRun}
collection={collection}
/>
@@ -116,10 +117,11 @@ const FormUrlEncodedParams = ({ item, collection }) => {
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}>
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

@@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import find from 'lodash/find';
import get from 'lodash/get';
import classnames from 'classnames';
import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
import QueryEditor from 'components/RequestPane/QueryEditor';
import Auth from 'components/RequestPane/Auth';
import GraphQLVariables from 'components/RequestPane/GraphQLVariables';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import Vars from 'components/RequestPane/Vars';
@@ -15,9 +15,9 @@ import Tests from 'components/RequestPane/Tests';
import { useTheme } from 'providers/Theme';
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { findEnvironmentInCollection } from 'utils/collections';
import useGraphqlSchema from './useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
import Documentation from 'components/Documentation/index';
import GraphQLSchemaActions from '../GraphQLSchemaActions/index';
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
const dispatch = useDispatch();
@@ -27,23 +27,11 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const variables = item.draft
? get(item, 'draft.request.body.graphql.variables')
: get(item, 'request.body.graphql.variables');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const { storedTheme } = useTheme();
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let { schema, loadSchema, isLoading: isSchemaLoading, error: schemaError } = useGraphqlSchema(url, environment);
const loadGqlSchema = () => {
if (!isSchemaLoading) {
loadSchema();
}
};
const [schema, setSchema] = useState(null);
useEffect(() => {
if (onSchemaLoad) {
onSchemaLoad(schema);
}
onSchemaLoad(schema);
}, [schema]);
const onQueryChange = (value) => {
@@ -90,6 +78,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'auth': {
return <Auth item={item} collection={collection} />;
}
case 'vars': {
return <Vars item={item} collection={collection} />;
}
@@ -102,6 +93,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
case 'tests': {
return <Tests item={item} collection={collection} />;
}
case 'docs': {
return <Documentation item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
@@ -135,6 +129,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
</div>
@@ -147,18 +144,10 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
<div className="flex items-center cursor-pointer hover:underline" onClick={loadGqlSchema}>
{isSchemaLoading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5} /> : null}
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5} /> : null}
<span className="ml-1">Schema</span>
</div>
<div className="flex items-center cursor-pointer hover:underline ml-2" onClick={toggleDocs}>
<IconBook size={18} strokeWidth={1.5} />
<span className="ml-1">Docs</span>
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs
</div>
<GraphQLSchemaActions item={item} collection={collection} onSchemaLoad={setSchema} toggleDocs={toggleDocs} />
</div>
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
</StyledWrapper>

View File

@@ -1,55 +0,0 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { buildClientSchema } from 'graphql';
import { fetchGqlSchema } from 'utils/network';
import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if (!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
} catch {
localStorage.setItem(localStorageKey, null);
return null;
}
});
const loadSchema = () => {
setIsLoading(true);
fetchGqlSchema(endpoint, environment)
.then((res) => res.data)
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoading(false);
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
toast.success('GraphQL Schema loaded successfully');
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occurred while loading GraphQL Schema');
});
};
return {
isLoading,
schema,
loadSchema,
error
};
};
export default useGraphqlSchema;

View File

@@ -0,0 +1,70 @@
import React, { useEffect, useRef, forwardRef } from 'react';
import useGraphqlSchema from './useGraphqlSchema';
import { IconBook, IconDownload, IconLoader2, IconRefresh } from '@tabler/icons';
import get from 'lodash/get';
import { findEnvironmentInCollection } from 'utils/collections';
import Dropdown from '../../Dropdown';
const GraphQLSchemaActions = ({ item, collection, onSchemaLoad, toggleDocs }) => {
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
const request = item.draft ? item.draft.request : item.request;
let {
schema,
schemaSource,
loadSchema,
isLoading: isSchemaLoading
} = useGraphqlSchema(url, environment, request, collection);
useEffect(() => {
if (onSchemaLoad) {
onSchemaLoad(schema);
}
}, [schema]);
const schemaDropdownTippyRef = useRef();
const onSchemaDropdownCreate = (ref) => (schemaDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="dropdown-icon cursor-pointer flex hover:underline ml-2">
{isSchemaLoading && <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />}
{!isSchemaLoading && schema && <IconRefresh size={18} strokeWidth={1.5} />}
{!isSchemaLoading && !schema && <IconDownload size={18} strokeWidth={1.5} />}
<span className="ml-1">Schema</span>
</div>
);
});
return (
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
<div className="flex items-center cursor-pointer hover:underline" onClick={toggleDocs}>
<IconBook size={18} strokeWidth={1.5} />
<span className="ml-1">Docs</span>
</div>
<Dropdown onCreate={onSchemaDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
onClick={(e) => {
schemaDropdownTippyRef.current.hide();
loadSchema('introspection');
}}
>
{schema && schemaSource === 'introspection' ? 'Refresh from Introspection' : 'Load from Introspection'}
</div>
<div
className="dropdown-item"
onClick={(e) => {
schemaDropdownTippyRef.current.hide();
loadSchema('file');
}}
>
Load from File
</div>
</Dropdown>
</div>
);
};
export default GraphQLSchemaActions;

View File

@@ -0,0 +1,89 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { buildClientSchema } from 'graphql';
import { fetchGqlSchema } from 'utils/network';
import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment, request, collection) => {
const { ipcRenderer } = window;
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schemaSource, setSchemaSource] = useState('');
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if (!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
} catch {
localStorage.setItem(localStorageKey, null);
return null;
}
});
const loadSchemaFromIntrospection = async () => {
const response = await fetchGqlSchema(endpoint, environment, request, collection);
if (!response) {
throw new Error('Introspection query failed');
}
if (response.status !== 200) {
throw new Error(response.statusText);
}
const data = response.data?.data;
if (!data) {
throw new Error('No data returned from introspection query');
}
setSchemaSource('introspection');
return data;
};
const loadSchemaFromFile = async () => {
const schemaContent = await ipcRenderer.invoke('renderer:load-gql-schema-file');
if (!schemaContent) {
setIsLoading(false);
return;
}
setSchemaSource('file');
return schemaContent.data;
};
const loadSchema = async (schemaSource) => {
if (isLoading) {
return;
}
setIsLoading(true);
try {
let data;
if (schemaSource === 'file') {
data = await loadSchemaFromFile();
} else {
// fallback to introspection if source is unknown
data = await loadSchemaFromIntrospection();
}
setSchema(buildClientSchema(data));
localStorage.setItem(localStorageKey, JSON.stringify(data));
toast.success('GraphQL Schema loaded successfully');
} catch (err) {
setError(err);
toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`);
}
setIsLoading(false);
};
return {
isLoading,
schema,
schemaSource,
loadSchema,
error
};
};
export default useGraphqlSchema;

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import get from 'lodash/get';
import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -10,6 +11,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
dispatch(
@@ -30,6 +32,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
collection={collection}
value={variables || ''}
theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -7,11 +7,15 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Auth from 'components/RequestPane/Auth';
import AuthMode from 'components/RequestPane/Auth/AuthMode';
import Vars from 'components/RequestPane/Vars';
import Assertions from 'components/RequestPane/Assertions';
import Script from 'components/RequestPane/Script';
import Tests from 'components/RequestPane/Tests';
import StyledWrapper from './StyledWrapper';
import { get } from 'lodash';
import Documentation from 'components/Documentation/index';
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch();
@@ -38,6 +42,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'auth': {
return <Auth item={item} collection={collection} />;
}
case 'vars': {
return <Vars item={item} collection={collection} />;
}
@@ -50,6 +57,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'tests': {
return <Tests item={item} collection={collection} />;
}
case 'docs': {
return <Documentation item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
@@ -71,39 +81,65 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
});
};
// get the length of active params, headers, asserts and vars
const params = item.draft ? get(item, 'draft.request.params', []) : get(item, 'request.params', []);
const headers = item.draft ? get(item, 'draft.request.headers', []) : get(item, 'request.headers', []);
const assertions = item.draft ? get(item, 'draft.request.assertions', []) : get(item, 'request.assertions', []);
const requestVars = item.draft ? get(item, 'draft.request.vars.req', []) : get(item, 'request.vars.req', []);
const responseVars = item.draft ? get(item, 'draft.request.vars.res', []) : get(item, 'request.vars.res', []);
const activeParamsLength = params.filter((param) => param.enabled).length;
const activeHeadersLength = headers.filter((header) => header.enabled).length;
const activeAssertionsLength = assertions.filter((assertion) => assertion.enabled).length;
const activeVarsLength =
requestVars.filter((request) => request.enabled).length +
responseVars.filter((response) => response.enabled).length;
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
Query
{activeParamsLength > 0 && <sup className="ml-1 font-medium">{activeParamsLength}</sup>}
</div>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
Body
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
{activeHeadersLength > 0 && <sup className="ml-1 font-medium">{activeHeadersLength}</sup>}
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
{activeVarsLength > 0 && <sup className="ml-1 font-medium">{activeVarsLength}</sup>}
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
</div>
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
Assert
{activeAssertionsLength > 0 && <sup className="ml-1 font-medium">{activeAssertionsLength}</sup>}
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
{/* Moved to post mvp */}
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs
</div>
{focusedTab.requestPaneTab === 'body' ? (
<div className="flex flex-grow justify-end items-center">
<RequestBodyMode item={item} collection={collection} />
</div>
) : null}
</div>
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
<section
className={`flex w-full ${
['script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'
}`}
>
{getTabPanel(focusedTab.requestPaneTab)}
</section>
</StyledWrapper>

View File

@@ -116,10 +116,11 @@ const MultipartFormParams = ({ item, collection }) => {
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}>
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

@@ -115,10 +115,11 @@ const QueryParams = ({ item, collection }) => {
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParam(param)}>
<button tabIndex="-1" onClick={() => handleRemoveParam(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

@@ -32,6 +32,50 @@ const Wrapper = styled.div`
position: relative;
top: 1px;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tooltiptext {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
color: ${(props) => props.theme.text};
text-align: center;
border-radius: 4px;
padding: 4px 8px;
position: absolute;
z-index: 1;
bottom: 34px;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
white-space: nowrap;
}
.tooltiptext::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -4px;
border-width: 4px;
border-style: solid;
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
}
.shortcut {
font-size: 0.625rem;
}
`;
export default Wrapper;

View File

@@ -5,8 +5,9 @@ import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/sli
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import HttpMethodSelector from './HttpMethodSelector';
import { useTheme } from 'providers/Theme';
import SendIcon from 'components/Icons/Send';
import { IconDeviceFloppy, IconArrowRight } from '@tabler/icons';
import SingleLineEditor from 'components/SingleLineEditor';
import { isMacOS } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper';
const QueryUrl = ({ item, collection, handleRun }) => {
@@ -14,6 +15,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const isMac = isMacOS();
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
@@ -22,7 +25,10 @@ const QueryUrl = ({ item, collection, handleRun }) => {
setMethodSelectorWidth(el.offsetWidth);
}, [method]);
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const onSave = () => {
dispatch(saveRequest(item.uid, collection.uid));
};
const onUrlChange = (value) => {
dispatch(
requestUrlChanged({
@@ -65,7 +71,25 @@ const QueryUrl = ({ item, collection, handleRun }) => {
collection={collection}
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<SendIcon color={theme.requestTabPanel.url.icon} width={22} />
<div
className="tooltip mr-3"
onClick={(e) => {
e.stopPropagation();
if (!item.draft) return;
onSave();
}}
>
<IconDeviceFloppy
color={item.draft ? theme.colors.text.yellow : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="tooltiptext text-xs">
Save <span className="shortcut">({saveShortcut})</span>
</span>
</div>
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
</div>
</div>
</StyledWrapper>

View File

@@ -4,7 +4,7 @@ const Wrapper = styled.div`
font-size: 0.8125rem;
.body-mode-selector {
background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color};
background: transparent;
border-radius: 3px;
.dropdown-item {
@@ -15,6 +15,10 @@ const Wrapper = styled.div`
.label-item {
padding: 0.2rem 0.6rem !important;
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
}
}
.caret {

View File

@@ -6,16 +6,19 @@ import { useDispatch } from 'react-redux';
import { updateRequestBodyMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestBodyMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
import { toastError } from 'utils/common/error';
const RequestBodyMode = ({ item, collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const bodyMode = body?.mode;
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none">
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none selected-body-mode">
{humanizeRequestBodyMode(bodyMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
</div>
);
@@ -31,6 +34,24 @@ const RequestBodyMode = ({ item, collection }) => {
);
};
const onPrettify = () => {
if (body?.json && bodyMode === 'json') {
try {
const bodyJson = JSON.parse(body.json);
const prettyBodyJson = JSON.stringify(bodyJson, null, 2);
dispatch(
updateRequestBody({
content: prettyBodyJson,
itemUid: item.uid,
collectionUid: collection.uid
})
);
} catch (e) {
toastError(new Error('Unable to prettify. Invalid JSON format.'));
}
}
};
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer body-mode-selector">
@@ -82,6 +103,15 @@ const RequestBodyMode = ({ item, collection }) => {
>
TEXT
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('sparql');
}}
>
SPARQL
</div>
<div className="label-item font-medium">Other</div>
<div
className="dropdown-item"
@@ -94,6 +124,11 @@ const RequestBodyMode = ({ item, collection }) => {
</div>
</Dropdown>
</div>
{bodyMode === 'json' && (
<button className="ml-1" onClick={onPrettify}>
Prettify
</button>
)}
</StyledWrapper>
);
};

View File

@@ -3,7 +3,7 @@ import get from 'lodash/get';
import CodeEditor from 'components/CodeEditor';
import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams';
import MultipartFormParams from 'components/RequestPane/MultipartFormParams';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -14,6 +14,7 @@ const RequestBody = ({ item, collection }) => {
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
dispatch(
@@ -28,17 +29,19 @@ const RequestBody = ({ item, collection }) => {
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
if (['json', 'xml', 'text'].includes(bodyMode)) {
if (['json', 'xml', 'text', 'sparql'].includes(bodyMode)) {
let codeMirrorMode = {
json: 'application/ld+json',
text: 'application/text',
xml: 'application/xml'
xml: 'application/xml',
sparql: 'application/sparql-query'
};
let bodyContent = {
json: body.json,
text: body.text,
xml: body.xml
xml: body.xml,
sparql: body.sparql
};
return (
@@ -46,6 +49,7 @@ const RequestBody = ({ item, collection }) => {
<CodeEditor
collection={collection}
theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}
onRun={onRun}

View File

@@ -8,6 +8,8 @@ import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'prov
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';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const RequestHeaders = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -91,6 +93,7 @@ const RequestHeaders = ({ item, collection }) => {
'name'
)
}
autocomplete={headerAutoCompleteList}
onRun={handleRun}
collection={collection}
/>
@@ -120,10 +123,11 @@ const RequestHeaders = ({ item, collection }) => {
<input
type="checkbox"
checked={header.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button onClick={() => handleRemoveHeader(header)}>
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -13,6 +13,7 @@ const Script = ({ item, collection }) => {
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onRequestScriptEdit = (value) => {
dispatch(
@@ -45,6 +46,7 @@ const Script = ({ item, collection }) => {
collection={collection}
value={requestScript || ''}
theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onRequestScriptEdit}
mode="javascript"
onRun={onRun}
@@ -57,6 +59,7 @@ const Script = ({ item, collection }) => {
collection={collection}
value={responseScript || ''}
theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onResponseScriptEdit}
mode="javascript"
onRun={onRun}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -12,6 +12,7 @@ const Tests = ({ item, collection }) => {
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
dispatch(
@@ -32,6 +33,7 @@ const Tests = ({ item, collection }) => {
collection={collection}
value={tests || ''}
theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -8,6 +8,7 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection
import SingleLineEditor from 'components/SingleLineEditor';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
const VarsTable = ({ item, collection, vars, varType }) => {
const dispatch = useDispatch();
@@ -29,7 +30,19 @@ const VarsTable = ({ item, collection, vars, varType }) => {
const _var = cloneDeep(v);
switch (type) {
case 'name': {
_var.name = e.target.value;
const value = e.target.value;
if (/^(?!\d).*$/.test(value) === false) {
toast.error('Variable names must not start with a number!');
return;
}
if (/^\w*$/.test(value) === false) {
toast.error('Variable contains invalid character! Variables must only contain alpha-numeric characters.');
return;
}
_var.name = value;
break;
}
case 'value': {
@@ -88,7 +101,7 @@ const VarsTable = ({ item, collection, vars, varType }) => {
</thead>
<tbody>
{vars && vars.length
? vars.map((_var, index) => {
? vars.map((_var) => {
return (
<tr key={_var.uid}>
<td>
@@ -128,10 +141,11 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<input
type="checkbox"
checked={_var.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button onClick={() => handleRemoveVar(_var)}>
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

@@ -1,42 +1,12 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
const Vars = ({ item, collection }) => {
const dispatch = useDispatch();
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
const { storedTheme } = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateRequestScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateResponseScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">

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