Compare commits

...

397 Commits

Author SHA1 Message Date
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
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
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
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
therealrinku
5ea9799c49 update: active vars count added 2023-10-11 15:34:09 +05:45
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
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
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
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
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
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
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
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
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
Anoop M D
45042cd52a Merge pull request #243 from LesageYann/feat/allow-async-tests
feat: allow test to be asynchrone
2023-09-29 12:42:57 +05:30
Lesage Yann
314e8c17d3 feat: allow test to be asynchrone 2023-09-29 08:55:26 +02:00
Anoop M D
69a7c0e4ce chore: bumped release version to v0.16.3 2023-09-29 12:24:17 +05:30
Anoop M D
626d925ad6 Merge pull request #242 from tpyle/bug/handle-collection-names-with-slashes
Corrects issue when collection names in imports have slashes
2023-09-29 12:20:11 +05:30
Thomas Pyle
2c0ccf769c Corrects issue when collection names in imports have slashes 2023-09-28 21:42:46 -04: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
516411b9a2 Merge pull request #240 from gkohen/main
Make sure path string does not overflow the dialog
2023-09-29 02:29:01 +05:30
Anoop M D
0f1e330dc5 feat(#119): ui placeholders for basic and bearer auths 2023-09-29 02:11:04 +05:30
Gabriel Kohen
60e3f3bb6a Make sure path string does not overflow the dialog
See also: #217
2023-09-28 16:22:40 -04:00
Anoop M D
51ee37cf96 feat(#119): bru lang support for basic and bearer auth 2023-09-29 01:35:22 +05:30
Anoop M D
a6b19605b5 Merge pull request #238 from jsoref/spelling
Spelling
2023-09-29 00:25:43 +05:30
Josh Soref
7ba471f26a spelling: serialization
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
f23dcf50a4 spelling: separator
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
86cda2cf5a spelling: sample
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
00b6e007af spelling: people
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
7313d1b4d7 spelling: occurred
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
8f803234ce spelling: javascript
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
76a743b74e spelling: interpreted
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
c623aa0909 spelling: header
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
3eb26834c7 spelling: github
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
64a5852227 spelling: evaluated
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
6471ca74c3 spelling: ephemeral
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 14:11:49 -04:00
Josh Soref
f77d955839 spelling: environments
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 13:24:00 -04:00
Josh Soref
9947a55b8d spelling: bottom
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 13:24:00 -04:00
Josh Soref
a71555725c spelling: being
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-09-28 13:24:00 -04:00
Anoop M D
c9ec6902a5 Merge pull request #234 from brahma-dev/main
Allow tabs in tablists to wrap.
2023-09-28 18:58:10 +05:30
Brahma Dev
c9c675e187 Allow tabs in tablists to wrap. 2023-09-28 13:11:21 +00:00
Anoop M D
0517b2685e fix(#233): bru cli fix for content header parsing issue 2023-09-28 18:31:42 +05:30
Anoop M D
5d01c0a765 chore: bump version to 0.16.2 2023-09-28 18:25:15 +05:30
Anoop M D
f3925923c9 fix(#233): fixed content type env var parsing issue 2023-09-28 18:24:10 +05:30
Anoop M D
6facdfd66b chore: bump version to v0.16.1 2023-09-28 10:27:45 +05:30
Anoop M D
0f211131b1 feat(#224): proxy config support in collection runner 2023-09-28 10:20:31 +05:30
Anoop M D
cd3b8a948e fix(#227): fixed json formatting issue 2023-09-28 10:15:54 +05:30
Anoop M D
f695036721 feat(#224): Bru CLI support for proxying requests 2023-09-28 05:26:09 +05:30
Anoop M D
3661fa7df3 chore: published libs 2023-09-28 05:17:59 +05:30
Anoop M D
559fcb0806 Merge pull request #225 from mirkogolze/feature/191-interpolate-header-names
#191 interpolate header names with variables
2023-09-28 04:38:37 +05:30
Anoop M D
d5da8a9e2f chore: bump version to v0.16.0 2023-09-28 04:34:34 +05:30
Anoop M D
a3050db6c4 fix(#216): fixed issue where .env vars were not injected into bru.getEnvVar() 2023-09-28 04:32:07 +05:30
Anoop M D
c27f090583 feat(#95): runner runs inside a tab of a collection view 2023-09-28 04:02:20 +05:30
Anoop M D
487dd73040 fix: fixed screen crash when collection was removed 2023-09-28 03:26:13 +05:30
Anoop M D
665428a2d0 feat(#224): proxy support feature - gui layer 2023-09-28 03:06:53 +05:30
Mirko Golze
6a2ba0f746 try other way to retrieve icon path for about window 2023-09-27 22:39:22 +02:00
Mirko Golze
36f9902f2e #191 interpolate header names with variables 2023-09-27 22:36:27 +02:00
Anoop M D
c0b7dad030 feat(#224): proxy support feature - electron layer 2023-09-28 00:58:05 +05:30
Anoop M D
8780d309ac feat: exposing chai library in script and test runtimes 2023-09-27 23:47:56 +05:30
Anoop M D
08c1563a7a chore: bump version to v0.15.3 2023-09-27 14:37:13 +05:30
Anoop M D
07ad1f9f60 fix(#217): Merge pull request #218 from tpyle/bug/no-environments
Adds fallback when no environments are defined
2023-09-27 14:35:26 +05:30
Thomas Pyle
8df6b241bb Adds fallback when no environments are defined 2023-09-26 19:39:46 -04:00
Anoop M D
50e0558d7d Merge pull request #215 from Cibico99/feature/XML-Format
Feature/xml format
2023-09-26 22:58:33 +05:30
Anoop M D
cbe84cc512 Merge pull request #213 from BrentShikoski/feature/license_all_modules
Add license to published npm modules.
2023-09-26 22:50:18 +05:30
Anoop M D
cbb975d81d Merge branch 'main' into feature/license_all_modules 2023-09-26 22:49:19 +05:30
Anoop M D
30ee472c40 release(#212): bru cli v0.9.0 2023-09-26 22:37:55 +05:30
Anoop M D
c7aecbea79 Merge pull request #212 from tpyle/feature/output-collection
Adds an option to collect output from cli runs
2023-09-26 22:09:57 +05:30
pedward99
b814c84411 Clean Up 2023-09-26 09:22:41 -04:00
Brent Shikoski
6306ad17c3 Add license information to modules. 2023-09-25 20:57:51 -05:00
Brent Shikoski
4b800e30e4 Merge branch 'usebruno:main' into feature/add_license_to_all_cli_dependent_modules 2023-09-25 19:37:10 -05:00
Thomas Pyle
89f418a114 Adds an option to collect output from cli runs 2023-09-25 17:48:53 -04:00
pedward99
9c8ef09d01 XML Format working 2023-09-25 13:13:14 -04:00
Brent Shikoski
83d354c25c Add license to modules the cli is dependent on.
- bruno-js
- bruno-lang
- bruno-query
2023-09-24 22:29:10 -05:00
Anoop M D
bb31ddc5d2 chore: release v0.15.2 2023-09-25 04:42:41 +05:30
Anoop M D
ff40178c8c fix(#210): fixing bruno libraries dep issues 2023-09-25 04:41:39 +05:30
Anoop M D
1c549f7faf fix: fixed issue related about-window dep breaking build 2023-09-25 02:54:36 +05:30
Anoop M D
eb6b75ff98 feat(#199): bru cli updates to load .env vars 2023-09-25 02:10:12 +05:30
Anoop M D
eb010adeac chore: added collection variables feature note 2023-09-25 01:09:25 +05:30
Anoop M D
7e5e22cfcf chore: release v0.15.0 2023-09-25 00:58:24 +05:30
Anoop M D
2515e78a10 feat(#200): req.setMaxRedirects() api 2023-09-25 00:09:29 +05:30
Anoop M D
511854369f feat(#205): collection properties dropdown 2023-09-24 23:53:31 +05:30
Anoop M D
18f185d37c chore: fixed env table styling issue 2023-09-24 23:31:48 +05:30
Anoop M D
7a0322d09e Merge pull request #209 from usebruno/feature/env-secrets
Feature/env secrets
2023-09-24 23:22:40 +05:30
Anoop M D
2dadad3af0 Merge branch 'main' into feature/env-secrets 2023-09-24 23:11:45 +05:30
Anoop M D
eaa31342dc Merge pull request #207 from mirkogolze/feature/env-secrets
#199 improve code to check given env vars correctly
2023-09-24 23:07:08 +05:30
Anoop M D
c4fd9d38a5 Merge pull request #208 from mirkogolze/feature/cli-env-vars
#199 bring feature cli overridable env vars to main
2023-09-24 23:05:19 +05:30
Anoop M D
9c4c219b99 feat(#199): Env Secrets - UI and Electron Layer updates 2023-09-24 23:02:39 +05:30
Mirko Golze
8e22aa2fca #199 improve code to check given envvars correctly 2023-09-24 15:40:04 +02:00
Mirko Golze
6b9e085696 #199 small code refactoring 2023-09-24 15:39:56 +02:00
Mirko Golze
74282706aa #199 add CLI feature to use command line parameter '--env-var secret=xzy123' 2023-09-24 15:39:51 +02:00
Mirko Golze
aa88aa73a2 #199 improve code to check given envvars correctly 2023-09-24 15:28:33 +02:00
Anoop M D
f78c1640e9 feat(#199): electron safeStorage util for storing secrets with aes256 fallback 2023-09-24 17:49:28 +05:30
Anoop M D
a5a17cf8eb fix(#131): fixed macos ctrl+a select all issue 2023-09-24 02:47:05 +05:30
Anoop M D
c5a86cb343 feat: documentation link in app titlebar 2023-09-24 02:33:58 +05:30
Anoop M D
9b94cddc9b Merge pull request #204 from jeffprinty/enhancement/add-about-window
Issue #203 Add about-window to help menu
2023-09-24 02:10:23 +05:30
Anoop M D
0a172ddce8 feat(#206): Collection and Env variables viewer 2023-09-24 02:07:31 +05:30
Jeff Printy
aea1cbba9e Issue #203 Add about-window to help menu 2023-09-22 23:17:53 -05:00
Anoop M D
7a1b44858d Merge pull request #202 from mirkogolze/feature/env-secrets
#199 add CLI feature to use command line parameter '--env-var' secret=…
2023-09-23 03:18:24 +05:30
Anoop M D
1c89ab3450 fix: fixed issue where vm2 instantiated objects were not being sent to renderer process 2023-09-23 03:14:27 +05:30
Anoop M D
e3ce420216 feat(#122): supporting process.env vars in UI and electron layer 2023-09-23 02:55:54 +05:30
Anoop M D
c91fef2264 chore: refactor electron storage related modules 2023-09-22 20:38:45 +05:30
Mirko Golze
c83fce16dc #199 small code refactoring 2023-09-22 09:22:46 +02:00
Mirko Golze
5415e20d7e #199 add CLI feature to use command line parameter '--env-var secret=xzy123' 2023-09-21 22:17:46 +02:00
Anoop M D
2f45b95930 feat(#199): bru lang updates to store environment secrets 2023-09-22 01:08:35 +05:30
Anoop M D
4531cfc994 chore(#197): ran prettier on tests folder 2023-09-22 00:45:42 +05:30
Anoop M D
bd0738198c chore(#197): ran prettier on packages/bruno-testbench 2023-09-22 00:44:47 +05:30
Anoop M D
9a81793151 chore(#197): ran prettier on packages/bruno-tauri 2023-09-22 00:44:28 +05:30
Anoop M D
88c16fa388 chore(#197): ran prettier on packages/bruno-schema 2023-09-22 00:43:56 +05:30
Anoop M D
f68eacfe0d chore(#197): ran prettier on packages/bruno-query 2023-09-22 00:43:31 +05:30
Anoop M D
116e050987 chore(#197): ran prettier on packages/bruno-lang 2023-09-22 00:42:48 +05:30
Anoop M D
5af2f68252 chore(#197): ran prettier on packages/bruno-js 2023-09-22 00:42:14 +05:30
Anoop M D
a53dd76854 chore(#197): ran prettier on packages/bruno-graphql-docs 2023-09-22 00:37:51 +05:30
Anoop M D
67fe264494 chore(#197): ran prettier on packages/bruno-electron 2023-09-22 00:36:42 +05:30
Anoop M D
ae692dde06 chore(#197): ran prettier on packages/bruno-cli 2023-09-22 00:34:11 +05:30
Anoop M D
1c4c5cc0c0 feat(#122): Support parsing of dotenv files 2023-09-22 00:27:27 +05:30
Anoop M D
19ca1af71e chore: release 0.14.1 2023-09-20 13:11:16 +05:30
Anoop M D
4016a83626 Merge pull request #201 from mirkogolze/bugfix/import-postman-header-check
#192 implement fallback to search body language by header 'content-type'
2023-09-20 13:04:25 +05:30
Mirko Golze
71b18c8b21 implement fallback to search body language by content-type header 2023-09-20 08:31:23 +02:00
Anoop M D
b53fcbb3d1 Merge pull request #198 from usebruno/feature/prettier-formatting
feat(#197): prettier formatting on all files in packages/bruno-app
2023-09-18 13:47:05 +05:30
Paul Edwards
aeb29393c5 Code Editor Mode and formatting 2023-07-19 20:06:37 -04:00
Paul Edwards
0866d33858 Merge branch 'main' of https://github.com/usebruno/bruno into main 2023-05-17 00:09:23 -04:00
Paul Edwards
ad905d1a0a XML Indenting with header check 2023-04-26 22:06:52 -04:00
296 changed files with 39138 additions and 3244 deletions

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

@@ -0,0 +1,46 @@
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: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

@@ -1,29 +1,35 @@
name: Unit Tests
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
run: npm run build --workspace=packages/bruno-query
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang
- name: Test Package bruno-schema
run: npm run test --workspace=packages/bruno-schema
- name: Test Package bruno-app
run: npm run test --workspace=packages/bruno-app
- name: Test Package bruno-js
run: npm run test --workspace=packages/bruno-js
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Check package-lock.json
run: npm ci
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
run: npm run build --workspace=packages/bruno-query
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang
- name: Test Package bruno-schema
run: npm run test --workspace=packages/bruno-schema
- name: Test Package bruno-app
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

5
.gitignore vendored
View File

@@ -4,7 +4,6 @@
node_modules
yarn.lock
pnpm-lock.yaml
package-lock.json
.pnp
.pnp.js
@@ -41,3 +40,7 @@ yarn-error.log*
/test-results/
/playwright-report/
/playwright/.cache/
#dev editor
bruno.iml
.idea

View File

@@ -1,3 +1,5 @@
**English** | [Русский](/contributing_ru.md)
## Lets make bruno better, together !!
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
@@ -19,7 +21,7 @@ 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

37
contributing_ru.md Normal file
View File

@@ -0,0 +1,37 @@
[English](/contributing.md) | **Русский**
## Давайте вместе сделаем Бруно лучше!!!
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
### Стек
Bruno построен с использованием NextJs и 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

View File

@@ -1,9 +1,12 @@
**English** | [Русский](/docs/development_ru.md)
## Development
Bruno is deing 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.
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
- NodeJS v18
### Local Development
@@ -15,7 +18,6 @@ nvm use
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

55
docs/development_ru.md Normal file
View File

@@ -0,0 +1,55 @@
[English](/docs/development.md) | **Русский**
## Разработка
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение 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
```

27978
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@
"packages/bruno-testbench",
"packages/bruno-graphql-docs"
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
@@ -30,7 +31,7 @@
"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:snap": "./scripts/build-electron.sh snap",
"test:e2e": "npx playwright test",
"test:report": "npx playwright show-report",
"prepare": "husky install"

View File

@@ -18,7 +18,7 @@
"@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.3.1",
"@usebruno/schema": "0.5.0",
"axios": "^0.26.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
@@ -27,13 +27,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",
@@ -47,11 +51,13 @@
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-hot-toast": "^2.4.0",
"react-inspector": "^6.0.2",
"react-redux": "^7.2.6",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
"styled-components": "^5.3.3",
"tailwindcss": "^2.2.19",
"xml-formatter": "^3.5.0",
"yup": "^0.32.11"
},
"devDependencies": {

View File

@@ -29,7 +29,7 @@ const BrunoSupport = ({ onClose }) => {
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">Github</span>
<span className="label ml-2">GitHub</span>
</a>
</div>
<div className="mt-2">

View File

@@ -80,7 +80,7 @@ export default class CodeEditor extends React.Component {
}
componentDidUpdate(prevProps) {
// Ensure the changes caused by this update are not interpretted as
// Ensure the changes caused by this update are not interpreted as
// user-input changes which could otherwise result in an infinite
// event loop.
this.ignoreChangeEvent = true;
@@ -119,7 +119,7 @@ export default class CodeEditor extends React.Component {
render() {
return (
<StyledWrapper
className="h-full"
className="h-full w-full"
aria-label="Code Editor"
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,192 @@
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', {});
console.log('saved auth', awsv4Auth);
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,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

@@ -0,0 +1,27 @@
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;
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,200 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
const ProxySettings = ({ proxyConfig, onUpdate }) => {
const formik = useFormik({
initialValues: {
enabled: proxyConfig.enabled || false,
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
},
validationSchema: Yup.object({
enabled: Yup.boolean(),
protocol: Yup.string().oneOf(['http', 'https', 'socks5']),
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)
})
}),
onSubmit: (values) => {
onUpdate(values);
}
});
useEffect(() => {
formik.setValues({
enabled: proxyConfig.enabled || false,
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
});
}, [proxyConfig]);
return (
<StyledWrapper>
<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 === '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="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="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="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="text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save
</button>
</div>
</form>
</StyledWrapper>
);
};
export default ProxySettings;

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

@@ -0,0 +1,46 @@
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 {
border: 1px solid ${(props) => props.theme.table.border};
li {
background-color: ${(props) => props.theme.bg} !important;
}
}
}
.muted {
color: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

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

@@ -0,0 +1,90 @@
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 Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
import Test from './Tests';
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 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 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} />;
}
}
};
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>
<section className={`flex ${['auth', 'script'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
</StyledWrapper>
);
};
export default CollectionSettings;

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

@@ -43,7 +43,7 @@ const Wrapper = styled.div`
}
&.border-top {
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
}
}
}

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

@@ -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')
}),
@@ -27,7 +27,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
toast.success('Environment created in collection');
onClose();
})
.catch(() => toast.error('An error occured while created the environment'));
.catch(() => toast.error('An error occurred while created the environment'));
}
});

View File

@@ -14,7 +14,7 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
toast.success('Environment deleted successfully');
onClose();
})
.catch(() => toast.error('An error occured while deleting the environment'));
.catch(() => toast.error('An error occurred while deleting the environment'));
};
return (

View File

@@ -5,10 +5,22 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
padding: 4px 10px;
&:nth-child(1),
&:nth-child(4),
&:nth-child(5) {
width: 70px;
}
&:nth-child(2) {
width: 25%;
}
}
thead {
@@ -16,7 +28,7 @@ const Wrapper = styled.div`
font-size: 0.8125rem;
user-select: none;
}
td {
thead td {
padding: 6px 10px;
}
}

View File

@@ -2,13 +2,16 @@ import React, { useReducer } 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';
const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { variables, hasChanges } = state;
@@ -20,7 +23,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
type: 'CHANGES_SAVED'
});
})
.catch(() => toast.error('An error occured while saving the changes'));
.catch(() => toast.error('An error occurred while saving the changes'));
};
const addVariable = () => {
@@ -44,6 +47,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
variable.enabled = e.target.checked;
break;
}
case 'secret': {
variable.secret = e.target.checked;
break;
}
}
reducerDispatch({
type: 'UPDATE_VAR',
@@ -63,8 +70,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
<table>
<thead>
<tr>
<td>Enabled</td>
<td>Name</td>
<td>Value</td>
<td>Secret</td>
<td></td>
</tr>
</thead>
@@ -73,6 +82,14 @@ const EnvironmentVariables = ({ environment, collection }) => {
? 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"
@@ -86,29 +103,25 @@ const EnvironmentVariables = ({ environment, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={variable.value}
className="mousetrap"
onChange={(e) => handleVarChange(e, variable, 'value')}
theme={storedTheme}
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={variable.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'enabled')}
/>
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
<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>
);

View File

@@ -12,6 +12,7 @@ const reducer = (state, action) => {
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
});
draft.hasChanges = true;
@@ -24,6 +25,7 @@ const reducer = (state, action) => {
variable.name = action.variable.name;
variable.value = action.variable.value;
variable.enabled = action.variable.enabled;
variable.secret = action.variable.secret;
draft.hasChanges = true;
});
}

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

@@ -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')
}),
@@ -27,7 +27,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
toast.success('Environment renamed successfully');
onClose();
})
.catch(() => toast.error('An error occured while renaming the environment'));
.catch(() => toast.error('An error occurred while renaming the environment'));
}
});

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);
@@ -19,7 +21,8 @@ const Wrapper = styled.div`
align-items: flex-start;
justify-content: center;
overflow-y: auto;
z-index: 1003;
z-index: 10;
background-color: rgba(0, 0, 0, 0.5);
}
.bruno-modal-card {
@@ -28,7 +31,7 @@ const Wrapper = styled.div`
background: var(--color-background-top);
border-radius: var(--border-radius);
position: relative;
z-index: 1003;
z-index: 10;
max-width: calc(100% - var(--spacing-base-unit));
box-shadow: var(--box-shadow-base);
display: flex;

View File

@@ -3,7 +3,7 @@ import StyledWrapper from './StyledWrapper';
const ModalHeader = ({ title, handleCancel }) => (
<div className="bruno-modal-header">
{title ? <div className="bruno-modal-heade-title">{title}</div> : null}
{title ? <div className="bruno-modal-header-title">{title}</div> : null}
{handleCancel ? (
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
×
@@ -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

@@ -27,7 +27,7 @@ const Support = () => {
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">Github</span>
<span className="label ml-2">GitHub</span>
</a>
</div>
<div className="mt-2">

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,206 @@
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', {});
console.log('saved auth', awsv4Auth);
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

@@ -116,10 +116,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

@@ -6,6 +6,7 @@ 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';
@@ -18,6 +19,7 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection
import { findEnvironmentInCollection } from 'utils/collections';
import useGraphqlSchema from './useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
import Documentation from 'components/Documentation/index';
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
const dispatch = useDispatch();
@@ -32,7 +34,14 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let { schema, loadSchema, isLoading: isSchemaLoading, error: schemaError } = useGraphqlSchema(url, environment);
const request = item.draft ? item.draft.request : item.request;
let {
schema,
loadSchema,
isLoading: isSchemaLoading,
error: schemaError
} = useGraphqlSchema(url, environment, request, collection);
const loadGqlSchema = () => {
if (!isSchemaLoading) {
@@ -90,6 +99,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 +114,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>;
}
@@ -114,7 +129,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occured!</div>;
return <div className="pb-4 px-4">An error occurred!</div>;
}
const getTabClassname = (tabName) => {
@@ -125,7 +140,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center tabs" role="tablist">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
Query
</div>
@@ -135,6 +150,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,6 +165,9 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs
</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}

View File

@@ -6,7 +6,7 @@ import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment) => {
const useGraphqlSchema = (endpoint, environment, request, collection) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
@@ -25,7 +25,7 @@ const useGraphqlSchema = (endpoint, environment) => {
const loadSchema = () => {
setIsLoading(true);
fetchGqlSchema(endpoint, environment)
fetchGqlSchema(endpoint, environment, request, collection)
.then((res) => res.data)
.then((s) => {
if (s && s.data) {
@@ -40,7 +40,7 @@ const useGraphqlSchema = (endpoint, environment) => {
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occured while loading GraphQL Schema');
toast.error('Error occurred while loading GraphQL Schema');
});
};

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>;
}
@@ -62,7 +72,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occured!</div>;
return <div className="pb-4 px-4">An error occurred!</div>;
}
const getTabClassname = (tabName) => {
@@ -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 items-center tabs" role="tablist">
<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

@@ -142,7 +142,7 @@ export default class QueryEditor extends React.Component {
}
componentDidUpdate(prevProps) {
// Ensure the changes caused by this update are not interpretted as
// Ensure the changes caused by this update are not interpreted as
// user-input changes which could otherwise result in an infinite
// event loop.
this.ignoreChangeEvent = true;

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

@@ -82,6 +82,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"

View File

@@ -28,17 +28,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 (

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();
@@ -72,19 +74,28 @@ const RequestHeaders = ({ item, collection }) => {
</thead>
<tbody>
{headers && headers.length
? headers.map((header, index) => {
? headers.map((header) => {
return (
<tr key={header.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={header.name}
className="mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'name')}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'name'
)
}
autocomplete={headerAutoCompleteList}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
@@ -112,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

@@ -128,10 +128,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

@@ -13,6 +13,8 @@ import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl';
import NetworkError from 'components/ResponsePane/NetworkError';
import RunnerResults from 'components/RunnerResults';
import VariablesEditor from 'components/VariablesEditor';
import CollectionSettings from 'components/CollectionSettings';
import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
@@ -110,7 +112,7 @@ const RequestTabPanel = () => {
}
if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
return <div className="pb-4 px-4">An error occured!</div>;
return <div className="pb-4 px-4">An error occurred!</div>;
}
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
@@ -118,11 +120,18 @@ const RequestTabPanel = () => {
return <div className="pb-4 px-4">Collection not found!</div>;
}
const showRunner = collection.showRunner;
if (showRunner) {
if (focusedTab.type === 'collection-runner') {
return <RunnerResults collection={collection} />;
}
if (focusedTab.type === 'variables') {
return <VariablesEditor collection={collection} />;
}
if (focusedTab.type === 'collection-settings') {
return <CollectionSettings collection={collection} />;
}
const item = findItemInCollection(collection, activeTabUid);
if (!item || !item.uid) {
return <RequestNotFound itemUid={activeTabUid} />;

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { IconFiles, IconRun } from '@tabler/icons';
import { uuid } from 'utils/common';
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import VariablesView from 'components/VariablesView';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux';
import { toggleRunnerView } from 'providers/ReduxStore/slices/collections';
import StyledWrapper from './StyledWrapper';
const CollectionToolBar = ({ collection }) => {
@@ -11,8 +11,30 @@ const CollectionToolBar = ({ collection }) => {
const handleRun = () => {
dispatch(
toggleRunnerView({
collectionUid: collection.uid
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'collection-runner'
})
);
};
const viewVariables = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'variables'
})
);
};
const viewCollectionSettings = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'collection-settings'
})
);
};
@@ -28,7 +50,12 @@ const CollectionToolBar = ({ collection }) => {
<span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
</span>
<VariablesView collection={collection} />
<span className="mr-3">
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</span>
<span className="mr-3">
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</span>
<EnvironmentSelector collection={collection} />
</div>
</div>

View File

@@ -0,0 +1,30 @@
import React from 'react';
import Modal from 'components/Modal';
const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) => {
const _handleCancel = ({ type }) => {
if (type === 'button') {
return onCloseWithoutSave();
}
return onCancel();
};
return (
<Modal
size="sm"
title="Unsaved changes"
confirmText="Save and Close"
cancelText="Close without saving"
handleConfirm={onSaveAndClose}
handleCancel={_handleCancel}
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
>
<div className="font-normal">You have unsaved changes in you request.</div>
</Modal>
);
};
export default ConfirmRequestClose;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { IconVariable, IconSettings, IconRun } from '@tabler/icons';
const SpecialTab = ({ handleCloseClick, type }) => {
const getTabInfo = (type) => {
switch (type) {
case 'collection-settings': {
return (
<>
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
<span className="ml-1">Collection</span>
</>
);
}
case 'variables': {
return (
<>
<IconVariable size={18} strokeWidth={1.5} className="text-yellow-600" />
<span className="ml-1">Variables</span>
</>
);
}
case 'collection-runner': {
return (
<>
<IconRun size={18} strokeWidth={1.5} className="text-yellow-600" />
<span className="ml-1">Runner</span>
</>
);
}
}
};
return (
<>
<div className="flex items-center tab-label pl-2">{getTabInfo(type)}</div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg>
</div>
</>
);
};
export default SpecialTab;

View File

@@ -1,13 +1,22 @@
import React from 'react';
import React, { useState } from 'react';
import get from 'lodash/get';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux';
import { findItemInCollection } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
import RequestTabNotFound from './RequestTabNotFound';
import ConfirmRequestClose from './ConfirmRequestClose';
import SpecialTab from './SpecialTab';
import { useTheme } from 'providers/Theme';
import darkTheme from 'themes/dark';
import lightTheme from 'themes/light';
const RequestTab = ({ tab, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [showConfirmClose, setShowConfirmClose] = useState(false);
const handleCloseClick = (event) => {
event.stopPropagation();
@@ -20,35 +29,38 @@ const RequestTab = ({ tab, collection }) => {
};
const getMethodColor = (method = '') => {
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
let color = '';
method = method.toLocaleLowerCase();
switch (method) {
case 'get': {
color = 'var(--color-method-get)';
color = theme.request.methods.get;
break;
}
case 'post': {
color = 'var(--color-method-post)';
color = theme.request.methods.post;
break;
}
case 'put': {
color = 'var(--color-method-put)';
color = theme.request.methods.put;
break;
}
case 'delete': {
color = 'var(--color-method-delete)';
color = theme.request.methods.delete;
break;
}
case 'patch': {
color = 'var(--color-method-patch)';
color = theme.request.methods.patch;
break;
}
case 'options': {
color = 'var(--color-method-options)';
color = theme.request.methods.options;
break;
}
case 'head': {
color = 'var(--color-method-head)';
color = theme.request.methods.head;
break;
}
}
@@ -56,6 +68,14 @@ const RequestTab = ({ tab, collection }) => {
return color;
};
if (['collection-settings', 'variables', 'collection-runner'].includes(tab.type)) {
return (
<StyledWrapper className="flex items-center justify-between tab-container px-1">
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
</StyledWrapper>
);
}
const item = findItemInCollection(collection, tab.uid);
if (!item) {
@@ -70,6 +90,39 @@ const RequestTab = ({ tab, collection }) => {
return (
<StyledWrapper className="flex items-center justify-between tab-container px-1">
{showConfirmClose && (
<ConfirmRequestClose
onCancel={() => setShowConfirmClose(false)}
onCloseWithoutSave={() => {
dispatch(
deleteRequestDraft({
itemUid: item.uid,
collectionUid: collection.uid
})
);
dispatch(
closeTabs({
tabUids: [tab.uid]
})
);
setShowConfirmClose(false);
}}
onSaveAndClose={() => {
dispatch(saveRequest(item.uid, collection.uid))
.then(() => {
dispatch(
closeTabs({
tabUids: [tab.uid]
})
);
setShowConfirmClose(false);
})
.catch((err) => {
console.log('err', err);
});
}}
/>
)}
<div className="flex items-baseline tab-label pl-2">
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
{method}
@@ -78,7 +131,14 @@ const RequestTab = ({ tab, collection }) => {
{item.name}
</span>
</div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
<div
className="flex px-2 close-icon-container"
onClick={(e) => {
if (!item.draft) return handleCloseClick(e);
setShowConfirmClose(true);
}}
>
{!item.draft ? (
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
const Wrapper = styled.div`
border-bottom: 1px solid ${(props) => props.theme.requestTabs.borromBorder};
border-bottom: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
ul {
padding: 0;

View File

@@ -76,9 +76,7 @@ const RequestTabs = () => {
});
};
const showRunner = activeCollection && activeCollection.showRunner;
// Todo: Must support ephermal requests
// Todo: Must support ephemeral requests
return (
<StyledWrapper className={getRootClassname()}>
{newRequestModalOpen && (
@@ -87,72 +85,70 @@ const RequestTabs = () => {
{collectionRequestTabs && collectionRequestTabs.length ? (
<>
<CollectionToolBar collection={activeCollection} />
{!showRunner ? (
<div className="flex items-center pl-4">
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={leftSlide}>
<div className="flex items-center">
<IconChevronLeft size={18} strokeWidth={1.5} />
</div>
</li>
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
<div className="flex items-center home-icon-container">
<IconHome2 size={18} strokeWidth={1.5}/>
</div>
</li> */}
</ul>
<ul role="tablist" style={{ maxWidth: maxTablistWidth }} ref={tabsRef}>
{collectionRequestTabs && collectionRequestTabs.length
? collectionRequestTabs.map((tab, index) => {
return (
<li
key={tab.uid}
className={getTabClassname(tab, index)}
role="tab"
onClick={() => handleClick(tab)}
>
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} />
</li>
);
})
: null}
</ul>
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={rightSlide}>
<div className="flex items-center">
<IconChevronRight size={18} strokeWidth={1.5} />
</div>
</li>
) : null}
<li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}>
<div className="flex items-center pl-4">
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={leftSlide}>
<div className="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
</svg>
<IconChevronLeft size={18} strokeWidth={1.5} />
</div>
</li>
{/* Moved to post mvp */}
{/* <li className="select-none new-tab choose-request">
) : null}
{/* Moved to post mvp */}
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
<div className="flex items-center home-icon-container">
<IconHome2 size={18} strokeWidth={1.5}/>
</div>
</li> */}
</ul>
<ul role="tablist" style={{ maxWidth: maxTablistWidth }} ref={tabsRef}>
{collectionRequestTabs && collectionRequestTabs.length
? collectionRequestTabs.map((tab, index) => {
return (
<li
key={tab.uid}
className={getTabClassname(tab, index)}
role="tab"
onClick={() => handleClick(tab)}
>
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} />
</li>
);
})
: null}
</ul>
<ul role="tablist">
{showChevrons ? (
<li className="select-none short-tab" onClick={rightSlide}>
<div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>
<IconChevronRight size={18} strokeWidth={1.5} />
</div>
</li> */}
</ul>
</div>
) : null}
</li>
) : null}
<li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}>
<div className="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
</svg>
</div>
</li>
{/* Moved to post mvp */}
{/* <li className="select-none new-tab choose-request">
<div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>
</div>
</li> */}
</ul>
</div>
</>
) : null}
</StyledWrapper>

View File

@@ -1,12 +1,13 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
position: absolute;
height: 100%;
z-index: 1;
background-color: ${(props) => props.theme.requestTabPanel.responseOverlayBg};
div.overlay {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
height: 100%;
z-index: 9;
display: flex;
flex-direction: column;
@@ -14,6 +15,11 @@ const StyledWrapper = styled.div`
padding-top: 20%;
overflow: hidden;
text-align: center;
.loading-icon {
transform: scaleY(-1);
animation: rotateCounterClockwise 1s linear infinite;
}
}
`;

View File

@@ -13,17 +13,17 @@ const ResponseLoadingOverlay = ({ item, collection }) => {
};
return (
<StyledWrapper className="mt-4 px-3 w-full">
<StyledWrapper className="px-3 w-full">
<div className="overlay">
<div style={{ marginBottom: 15, fontSize: 26 }}>
<div style={{ display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5 }}>
<div style={{ display: 'inline-block', fontSize: 20, marginLeft: 5, marginRight: 5 }}>
<StopWatch />
</div>
</div>
<IconRefresh size={24} className="animate-spin" />
<IconRefresh size={24} className="loading-icon" />
<button
onClick={handleCancelRequest}
className="mt-4 uppercase btn-md rounded btn-secondary ease-linear transition-all duration-150"
className="mt-4 uppercase btn-sm rounded btn-secondary ease-linear transition-all duration-150"
type="button"
>
Cancel Request

View File

@@ -1,8 +1,14 @@
import React from 'react';
import { IconSend } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
import { isMacOS } from 'utils/common/platform';
const Placeholder = () => {
const isMac = isMacOS();
const sendRequestShortcut = isMac ? 'Cmd + Enter' : 'Ctrl + Enter';
const newRequestShortcut = isMac ? 'Cmd + B' : 'Ctrl + B';
const editEnvironmentShortcut = isMac ? 'Cmd + E' : 'Ctrl + E';
return (
<StyledWrapper>
<div className="send-icon flex justify-center" style={{ fontSize: 200 }}>
@@ -13,13 +19,11 @@ const Placeholder = () => {
<div className="px-1 py-2">Send Request</div>
<div className="px-1 py-2">New Request</div>
<div className="px-1 py-2">Edit Environments</div>
<div className="px-1 py-2">Help</div>
</div>
<div className="flex flex-1 flex-col px-1">
<div className="px-1 py-2">Cmd + Enter</div>
<div className="px-1 py-2">Cmd + B</div>
<div className="px-1 py-2">Cmd + E</div>
<div className="px-1 py-2">Cmd + H</div>
<div className="px-1 py-2">{sendRequestShortcut}</div>
<div className="px-1 py-2">{newRequestShortcut}</div>
<div className="px-1 py-2">{editEnvironmentShortcut}</div>
</div>
</div>
</StyledWrapper>

View File

@@ -1,9 +1,27 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: grid;
grid-template-columns: 100%;
grid-template-rows: 1.25rem calc(100% - 1.25rem);
/* This is a hack to force Codemirror to use all available space */
> div {
position: relative;
}
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
position: absolute;
top: 0;
bottom: 0;
height: 100%;
width: 100%;
}
div[role='tablist'] {
.active {
color: ${(props) => props.theme.colors.text.yellow};
}
}
`;

View File

@@ -3,12 +3,61 @@ import CodeEditor from 'components/CodeEditor';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import classnames from 'classnames';
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
import { getCodeMirrorModeBasedOnContentType } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper';
import { useState } from 'react';
import { useMemo } from 'react';
const QueryResult = ({ item, collection, value, width, disableRunEventListener, mode }) => {
const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => {
const { storedTheme } = useTheme();
const [tab, setTab] = useState('preview');
const dispatch = useDispatch();
const contentType = getContentType(headers);
const mode = getCodeMirrorModeBasedOnContentType(contentType);
const formatResponse = (data, mode) => {
if (!data) {
return '';
}
if (mode.includes('json')) {
return safeStringifyJSON(data, true);
}
if (mode.includes('xml')) {
let parsed = safeParseXML(data, { collapseContent: true });
if (typeof parsed === 'string') {
return parsed;
}
return safeStringifyJSON(parsed, true);
}
if (['text', 'html'].includes(mode)) {
if (typeof data === 'string') {
return data;
}
return safeStringifyJSON(data);
}
if (mode.includes('image')) {
return item.requestSent.url;
}
// final fallback
if (typeof data === 'string') {
return data;
}
return safeStringifyJSON(data);
};
const value = formatResponse(data, mode);
const onRun = () => {
if (disableRunEventListener) {
@@ -17,18 +66,60 @@ const QueryResult = ({ item, collection, value, width, disableRunEventListener,
dispatch(sendRequest(item, collection.uid));
};
return (
<StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}>
<div className="h-full">
<CodeEditor
collection={collection}
theme={storedTheme}
onRun={onRun}
value={value || ''}
mode={mode}
readOnly
const getTabClassname = (tabName) => {
return classnames(`select-none ${tabName}`, {
active: tabName === tab,
'cursor-pointer': tabName !== tab
});
};
const getTabs = () => {
if (!mode.includes('html')) {
return null;
}
return (
<>
<div className={getTabClassname('raw')} role="tab" onClick={() => setTab('raw')}>
Raw
</div>
<div className={getTabClassname('preview')} role="tab" onClick={() => setTab('preview')}>
Preview
</div>
</>
);
};
const activeResult = useMemo(() => {
if (
tab === 'preview' &&
mode.includes('html') &&
item.requestSent &&
item.requestSent.url &&
typeof data === 'string'
) {
// Add the Base tag to the head so content loads properly. This also needs the correct CSP settings
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent.url}">`);
return (
<webview
src={`data:text/html; charset=utf-8,${encodeURIComponent(webViewSrc)}`}
webpreferences="disableDialogs=true, javascript=yes"
className="h-full bg-white"
/>
);
} else if (mode.includes('image')) {
return <img src={item.requestSent.url} alt="image" />;
}
return <CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value} mode={mode} readOnly />;
}, [tab, collection, storedTheme, onRun, value, mode]);
return (
<StyledWrapper className="w-full h-full" style={{ maxWidth: width }}>
<div className="flex justify-end gap-2 text-xs" role="tablist">
{getTabs()}
</div>
{error ? <span className="text-red-500">{error}</span> : activeResult}
</StyledWrapper>
);
};

View File

@@ -3,7 +3,7 @@ import StyledWrapper from './StyledWrapper';
const ResponseHeaders = ({ headers }) => {
return (
<StyledWrapper className="px-3 pb-4 w-full">
<StyledWrapper className="pb-4 w-full">
<table>
<thead>
<tr>

View File

@@ -7,7 +7,7 @@ const ResponseSize = ({ size }) => {
if (size > 1024) {
// size is greater than 1kb
let kb = Math.floor(size / 1024);
let decimal = ((size % 1024) / 1024).toFixed(2) * 100;
let decimal = Math.round(((size % 1024) / 1024).toFixed(2) * 100);
sizeToDisplay = kb + '.' + decimal + 'KB';
} else {
sizeToDisplay = size + 'B';

View File

@@ -15,8 +15,8 @@ const TestResults = ({ results, assertionResults }) => {
const failedAssertions = assertionResults.filter((result) => result.status === 'fail');
return (
<StyledWrapper className="flex flex-col px-3">
<div className="py-2 font-medium test-summary">
<StyledWrapper className="flex flex-col">
<div className="pb-2 font-medium test-summary">
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
</div>
<ul className="">

View File

@@ -20,7 +20,7 @@ const Timeline = ({ request, response }) => {
let requestData = safeStringifyJSON(request.data);
return (
<StyledWrapper className="px-3 pb-4 w-full">
<StyledWrapper className="pb-4 w-full">
<div>
<pre className="line request font-bold">
<span className="arrow">{'>'}</span> {request.method} {request.url}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import find from 'lodash/find';
import classnames from 'classnames';
import { safeStringifyJSON } from 'utils/common';
import { useDispatch, useSelector } from 'react-redux';
import { updateResponsePaneTab } from 'providers/ReduxStore/slices/tabs';
import QueryResult from './QueryResult';
@@ -41,10 +40,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
item={item}
collection={collection}
width={rightPaneWidth}
value={
response.data ? (isJson(response.headers) ? safeStringifyJSON(response.data, true) : response.data) : ''
}
mode={getContentType(response.headers)}
data={response.data}
headers={response.headers}
error={response.error}
/>
);
}
@@ -64,15 +62,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
}
};
if (isLoading) {
if (isLoading && !item.response) {
return (
<StyledWrapper className="flex h-full relative">
<StyledWrapper className="flex flex-col h-full relative">
<Overlay item={item} collection={collection} />
</StyledWrapper>
);
}
if (response.state !== 'success') {
if (!item.response) {
return (
<StyledWrapper className="flex h-full relative">
<Placeholder />
@@ -95,31 +93,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
});
};
const getContentType = (headers) => {
if (headers && headers.length) {
let contentType = headers
.filter((header) => header[0].toLowerCase() === 'content-type')
.map((header) => {
return header[1];
});
if (contentType && contentType.length) {
if (typeof contentType[0] == 'string' && /^[\w\-]+\/([\w\-]+\+)?json/.test(contentType[0])) {
return 'application/ld+json';
} else if (typeof contentType[0] == 'string' && /^[\w\-]+\/([\w\-]+\+)?xml/.test(contentType[0])) {
return 'application/xml';
}
}
}
return '';
};
const isJson = (headers) => {
return getContentType(headers) === 'application/ld+json';
};
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center px-3 tabs" role="tablist">
<div className="flex flex-wrap items-center pl-3 pr-4 tabs" role="tablist">
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>
Response
</div>
@@ -140,7 +116,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
</div>
) : null}
</div>
<section className="flex flex-grow mt-5">{getTabPanel(focusedTab.responsePaneTab)}</section>
<section
className={`flex flex-grow relative pl-3 pr-4 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{getTabPanel(focusedTab.responsePaneTab)}
</section>
</StyledWrapper>
);
};

View File

@@ -33,7 +33,8 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
collection={collection}
width={rightPaneWidth}
disableRunEventListener={true}
value={responseReceived && responseReceived.data ? safeStringifyJSON(responseReceived.data, true) : ''}
data={responseReceived.data}
headers={responseReceived.headers}
/>
);
}

View File

@@ -3,7 +3,7 @@ import path from 'path';
import { useDispatch } from 'react-redux';
import { get, each, cloneDeep } from 'lodash';
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
import { closeCollectionRunner } from 'providers/ReduxStore/slices/collections';
import { resetCollectionRunner } from 'providers/ReduxStore/slices/collections';
import { findItemInCollection, getTotalRequestCountInCollection } from 'utils/collections';
import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun } from '@tabler/icons';
import slash from 'utils/common/slash';
@@ -69,9 +69,9 @@ export default function RunnerResults({ collection }) {
dispatch(runCollectionFolder(collection.uid, runnerInfo.folderUid, runnerInfo.isRecursive));
};
const closeRunner = () => {
const resetRunner = () => {
dispatch(
closeCollectionRunner({
resetCollectionRunner({
collectionUid: collection.uid
})
);
@@ -101,8 +101,8 @@ export default function RunnerResults({ collection }) {
Run Collection
</button>
<button className="submit btn btn-sm btn-close mt-6 ml-3" onClick={closeRunner}>
Close
<button className="submit btn btn-sm btn-close mt-6 ml-3" onClick={resetRunner}>
Reset
</button>
</StyledWrapper>
);
@@ -202,8 +202,8 @@ export default function RunnerResults({ collection }) {
<button type="submit" className="submit btn btn-sm btn-secondary mt-6 ml-3" onClick={runCollection}>
Run Collection
</button>
<button className="btn btn-sm btn-close mt-6 ml-3" onClick={closeRunner}>
Close
<button className="btn btn-sm btn-close mt-6 ml-3" onClick={resetRunner}>
Reset
</button>
</div>
) : null}

View File

@@ -18,7 +18,7 @@ const CloneCollectionItem = ({ collection, item, 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')
}),
@@ -28,7 +28,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
onClose();
})
.catch((err) => {
toast.error(err ? err.message : 'An error occured while cloning the request');
toast.error(err ? err.message : 'An error occurred while cloning the request');
});
}
});

View File

@@ -0,0 +1,21 @@
import CodeEditor from 'components/CodeEditor/index';
import { HTTPSnippet } from 'httpsnippet';
import { useTheme } from 'providers/Theme/index';
import { buildHarRequest } from 'utils/codegenerator/har';
const CodeView = ({ language, item }) => {
const { storedTheme } = useTheme();
const { target, client, language: lang } = language;
let snippet = '';
try {
snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client);
} catch (e) {
console.error(e);
snippet = 'Error generating code snippet';
}
return <CodeEditor readOnly value={snippet} theme={storedTheme} mode={lang} />;
};
export default CodeView;

View File

@@ -0,0 +1,38 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
margin-inline: -1rem;
margin-block: -1.5rem;
background-color: ${(props) => props.theme.collection.environment.settings.bg};
.generate-code-sidebar {
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;
}
.generate-code-item {
min-width: 150px;
display: block;
position: relative;
cursor: pointer;
padding: 8px 10px;
border-left: solid 2px transparent;
text-decoration: none;
&:hover {
text-decoration: none;
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
}
}
.active {
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
&:hover {
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,149 @@
import Modal from 'components/Modal/index';
import { useState } from 'react';
import CodeView from './CodeView';
import StyledWrapper from './StyledWrapper';
import { isValidUrl } from 'utils/url/index';
import get from 'lodash/get';
import handlebars from 'handlebars';
import { findEnvironmentInCollection } from 'utils/collections';
const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) => {
if (!url || !url.length || typeof url !== 'string') {
return;
}
const template = handlebars.compile(url, { noEscape: true });
return template({
...envVars,
...collectionVariables,
process: {
env: {
...processEnvVars
}
}
});
};
const languages = [
{
name: 'HTTP',
target: 'http',
client: 'http1.1'
},
{
name: 'JavaScript-Fetch',
target: 'javascript',
client: 'fetch'
},
{
name: 'Javascript-jQuery',
target: 'javascript',
client: 'jquery'
},
{
name: 'Javascript-axios',
target: 'javascript',
client: 'axios'
},
{
name: 'Python-Python3',
target: 'python',
client: 'python3'
},
{
name: 'Python-Requests',
target: 'python',
client: 'requests'
},
{
name: 'PHP',
target: 'php',
client: 'curl'
},
{
name: 'Shell-curl',
target: 'shell',
client: 'curl'
},
{
name: 'Shell-httpie',
target: 'shell',
client: 'httpie'
}
];
const GenerateCodeItem = ({ collection, item, onClose }) => {
const url = get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url');
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let envVars = {};
if (environment) {
const vars = get(environment, 'variables', []);
envVars = vars.reduce((acc, curr) => {
acc[curr.name] = curr.value;
return acc;
}, {});
}
const interpolatedUrl = interpolateUrl({
url,
envVars,
collectionVariables: collection.collectionVariables,
processEnvVars: collection.processEnvVariables
});
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
return (
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
<StyledWrapper>
<div className="flex w-full">
<div>
<div className="generate-code-sidebar">
{languages &&
languages.length &&
languages.map((language) => (
<div
key={language.name}
className={
language.name === selectedLanguage.name ? 'generate-code-item active' : 'generate-code-item'
}
onClick={() => setSelectedLanguage(language)}
>
<span className="capitalize">{language.name}</span>
</div>
))}
</div>
</div>
<div className="flex-grow p-4">
{isValidUrl(interpolatedUrl) ? (
<CodeView
language={selectedLanguage}
item={{
...item,
request:
item.request.url !== ''
? {
...item.request,
url: interpolatedUrl
}
: {
...item.draft.request,
url: interpolatedUrl
}
}}
/>
) : (
<div className="flex flex-col justify-center items-center w-full">
<div className="text-center">
<h1 className="text-2xl font-bold">Invalid URL: {interpolatedUrl}</h1>
<p className="text-gray-500">Please check the URL and try again</p>
</div>
</div>
)}
</div>
</div>
</StyledWrapper>
</Modal>
);
};
export default GenerateCodeItem;

View File

@@ -17,7 +17,7 @@ const RenameCollectionItem = ({ collection, item, 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

@@ -25,13 +25,13 @@ const Wrapper = styled.div`
color: ${(props) => props.theme.request.methods.delete};
}
.method-patch {
color: ${(props) => props.theme.request.methods.put};
color: ${(props) => props.theme.request.methods.patch};
}
.method-options {
color: ${(props) => props.theme.request.methods.put};
color: ${(props) => props.theme.request.methods.options};
}
.method-head {
color: ${(props) => props.theme.request.methods.put};
color: ${(props) => props.theme.request.methods.head};
}
`;

View File

@@ -15,7 +15,8 @@ const RequestMethod = ({ item }) => {
'method-put': method === 'put',
'method-delete': method === 'delete',
'method-patch': method === 'patch',
'method-head': method === 'head'
'method-head': method === 'head',
'method-options': method == 'options'
});
};

View File

@@ -1,9 +1,10 @@
import React from 'react';
import get from 'lodash/get';
import { uuid } from 'utils/common';
import Modal from 'components/Modal';
import { useDispatch } from 'react-redux';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
import { showRunnerView } from 'providers/ReduxStore/slices/collections';
import { flattenItems } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
@@ -12,8 +13,10 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
const onSubmit = (recursive) => {
dispatch(
showRunnerView({
collectionUid: collection.uid
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'collection-runner'
})
);
dispatch(runCollectionFolder(collection.uid, item ? item.uid : null, recursive));

View File

@@ -6,7 +6,7 @@ import { useDrag, useDrop } from 'react-dnd';
import { IconChevronRight, IconDots } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
import { collectionFolderClicked, hideRunnerView } from 'providers/ReduxStore/slices/collections';
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
import { moveItem } from 'providers/ReduxStore/slices/collections/actions';
import Dropdown from 'components/Dropdown';
import NewRequest from 'components/Sidebar/NewRequest';
@@ -16,11 +16,12 @@ import RenameCollectionItem from './RenameCollectionItem';
import CloneCollectionItem from './CloneCollectionItem';
import DeleteCollectionItem from './DeleteCollectionItem';
import RunCollectionItem from './RunCollectionItem';
import GenerateCodeItem from './GenerateCodeItem';
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
import { getDefaultRequestPaneTab } from 'utils/collections';
import { hideHomePage } from 'providers/ReduxStore/slices/app';
import toast from 'react-hot-toast';
import StyledWrapper from './StyledWrapper';
const CollectionItem = ({ item, collection, searchText }) => {
@@ -32,6 +33,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false);
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
@@ -86,38 +88,51 @@ const CollectionItem = ({ item, collection, searchText }) => {
});
const handleClick = (event) => {
dispatch(
hideRunnerView({
collectionUid: collection.uid
})
);
if (isItemARequest(item)) {
if (itemIsOpenedInTabs(item, tabs)) {
switch (event.button) {
case 0: // left click
if (isItemARequest(item)) {
dispatch(hideHomePage());
if (itemIsOpenedInTabs(item, tabs)) {
dispatch(
focusTab({
uid: item.uid
})
);
return;
}
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab(item)
})
);
return;
}
dispatch(
focusTab({
uid: item.uid
collectionFolderClicked({
itemUid: item.uid,
collectionUid: collection.uid
})
);
} else {
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab(item)
})
);
}
dispatch(hideHomePage());
} else {
dispatch(
collectionFolderClicked({
itemUid: item.uid,
collectionUid: collection.uid
})
);
return;
case 2: // right click
const _menuDropdown = dropdownTippyRef.current;
if (_menuDropdown) {
let menuDropdownBehavior = 'show';
if (_menuDropdown.state.isShown) {
menuDropdownBehavior = 'hide';
}
_menuDropdown[menuDropdownBehavior]();
}
return;
}
};
const handleDoubleClick = (event) => {
setRenameItemModalOpen(true);
};
let indents = range(item.depth);
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const isFolder = isItemAFolder(item);
@@ -147,7 +162,15 @@ const CollectionItem = ({ item, collection, searchText }) => {
const sortFolderItems = (items = []) => {
return items.sort((a, b) => a.name.localeCompare(b.name));
};
const handleGenerateCode = (e) => {
e.stopPropagation();
dropdownTippyRef.current.hide();
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
setGenerateCodeItemModalOpen(true);
} else {
toast.error('URL is required');
}
};
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
@@ -171,13 +194,17 @@ const CollectionItem = ({ item, collection, searchText }) => {
{runCollectionModalOpen && (
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
)}
{generateCodeItemModalOpen && (
<GenerateCodeItem collection={collection} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
)}
<div className={itemRowClassName} ref={(node) => drag(drop(node))}>
<div className="flex items-center h-full w-full">
{indents && indents.length
? indents.map((i) => {
return (
<div
onClick={handleClick}
onMouseUp={handleClick}
onDoubleClick={handleDoubleClick}
className="indent-block"
key={i}
style={{
@@ -192,7 +219,8 @@ const CollectionItem = ({ item, collection, searchText }) => {
})
: null}
<div
onClick={handleClick}
onMouseUp={handleClick}
onDoubleClick={handleDoubleClick}
className="flex flex-grow items-center h-full overflow-hidden"
style={{
paddingLeft: 8
@@ -269,6 +297,16 @@ const CollectionItem = ({ item, collection, searchText }) => {
Clone
</div>
)}
{!isFolder && item.type === 'http-request' && (
<div
className="dropdown-item"
onClick={(e) => {
handleGenerateCode(e);
}}
>
Generate Code
</div>
)}
<div
className="dropdown-item delete-item"
onClick={(e) => {

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