mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-05 02:18:32 +00:00
Compare commits
411 Commits
version/1.
...
feature/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3a99a4d85 | ||
|
|
bbfa2b39a0 | ||
|
|
1a93eabf01 | ||
|
|
df1c5f9363 | ||
|
|
803d2d96c9 | ||
|
|
99873af281 | ||
|
|
1b63798ff3 | ||
|
|
c90d607046 | ||
|
|
c6c3931446 | ||
|
|
10e872c6ab | ||
|
|
6792cc26bd | ||
|
|
c76d99d1b0 | ||
|
|
b813c916b8 | ||
|
|
fab9d00566 | ||
|
|
afcd7395d9 | ||
|
|
ed9c61908d | ||
|
|
999e3e5b71 | ||
|
|
81ae8db1a9 | ||
|
|
f2b5b6f783 | ||
|
|
e8eab46f48 | ||
|
|
bb913d32bc | ||
|
|
2ea59dcdae | ||
|
|
bbdf514098 | ||
|
|
a0950dc4f3 | ||
|
|
d65ae78119 | ||
|
|
e6afbc75ff | ||
|
|
47e420dec1 | ||
|
|
1d6566679b | ||
|
|
535865fdeb | ||
|
|
5065b2ac37 | ||
|
|
6349e9b816 | ||
|
|
eb70883127 | ||
|
|
1e83b3b35c | ||
|
|
ef18805008 | ||
|
|
5d51a528d7 | ||
|
|
ff0ceb2879 | ||
|
|
4d7c044eba | ||
|
|
3a92cb4eda | ||
|
|
6244679d5b | ||
|
|
59c1b6b675 | ||
|
|
92a0f093db | ||
|
|
39dccd4b5f | ||
|
|
674820f7c9 | ||
|
|
f138b126f3 | ||
|
|
efaac453ce | ||
|
|
879c124aec | ||
|
|
9fe13f1868 | ||
|
|
2bbfb28090 | ||
|
|
3c65642e92 | ||
|
|
cf5f52b7b9 | ||
|
|
04d0439c9d | ||
|
|
f1116c3008 | ||
|
|
bbf4ad6b98 | ||
|
|
3fe3eec465 | ||
|
|
a93b05fd6e | ||
|
|
da25d46df4 | ||
|
|
0d13d40cd7 | ||
|
|
4664fd60b5 | ||
|
|
65ba984c2f | ||
|
|
8355b67bae | ||
|
|
9b3fe2fd97 | ||
|
|
34614f039f | ||
|
|
acd42eaa1b | ||
|
|
aebc8241cc | ||
|
|
0eda1b761d | ||
|
|
a05f7cb686 | ||
|
|
745a71700c | ||
|
|
ac9c190b41 | ||
|
|
1a1a230a1e | ||
|
|
b2e02b7762 | ||
|
|
9cbfeccbed | ||
|
|
4725300c41 | ||
|
|
f2aedf780d | ||
|
|
f03047a2f9 | ||
|
|
a7ba23d97e | ||
|
|
2521e980ea | ||
|
|
1c118fa04a | ||
|
|
b6fb5e02d4 | ||
|
|
5313704d84 | ||
|
|
b147f14fef | ||
|
|
66fe1528df | ||
|
|
a598cda624 | ||
|
|
69f218cc16 | ||
|
|
e1c12ea699 | ||
|
|
9801e91720 | ||
|
|
364fb45e97 | ||
|
|
5c9981aca2 | ||
|
|
fc697bf81b | ||
|
|
9bc07afc77 | ||
|
|
e4ae857df3 | ||
|
|
9e628fa6be | ||
|
|
3d26833b8a | ||
|
|
1089a52171 | ||
|
|
9dde2df475 | ||
|
|
1cc94e8ffe | ||
|
|
223f79a3e2 | ||
|
|
5dc6f6757d | ||
|
|
e20fe790a6 | ||
|
|
a006fe8230 | ||
|
|
577d54b432 | ||
|
|
afaebf6b3d | ||
|
|
6e89001825 | ||
|
|
cb611c6510 | ||
|
|
e7dd78ea53 | ||
|
|
9ad0f2d169 | ||
|
|
bf19645282 | ||
|
|
bb01199877 | ||
|
|
5627c5624f | ||
|
|
8e23a7054f | ||
|
|
d820069371 | ||
|
|
6f9daadcfb | ||
|
|
2de9b87c6f | ||
|
|
8d5d952026 | ||
|
|
178773d63a | ||
|
|
7994946c85 | ||
|
|
b020255269 | ||
|
|
afb2d3dffd | ||
|
|
73b0f0919d | ||
|
|
8975b9eef6 | ||
|
|
865e813b42 | ||
|
|
51f36b1903 | ||
|
|
6b122d7262 | ||
|
|
9f1aed3209 | ||
|
|
ce1110bdd4 | ||
|
|
788569a5f4 | ||
|
|
91397eaf57 | ||
|
|
c293ceefcf | ||
|
|
a8e5ce9c13 | ||
|
|
8ac916b0ff | ||
|
|
8d860a051c | ||
|
|
256f63dd38 | ||
|
|
0948964677 | ||
|
|
1b52bb27f7 | ||
|
|
4ac2c4ac34 | ||
|
|
7c27193983 | ||
|
|
2c3d2ff6a7 | ||
|
|
a4fff01647 | ||
|
|
2cd985faf7 | ||
|
|
9a35302d4b | ||
|
|
553f7675f2 | ||
|
|
3e714ab9f8 | ||
|
|
f2e9a6a502 | ||
|
|
b924e15afa | ||
|
|
b0c74909ba | ||
|
|
548a6b4319 | ||
|
|
b299879b82 | ||
|
|
3696562414 | ||
|
|
e02c6c274b | ||
|
|
9c9afaf78f | ||
|
|
6cde453032 | ||
|
|
8f06889996 | ||
|
|
52662f0766 | ||
|
|
ab0a4b8140 | ||
|
|
1b268ae9db | ||
|
|
8debb9fd11 | ||
|
|
7c07488e16 | ||
|
|
6073a9e2c3 | ||
|
|
9c652f86c9 | ||
|
|
3c0090d86f | ||
|
|
9132755d49 | ||
|
|
2a44691cb3 | ||
|
|
0d8a696498 | ||
|
|
bfa2706598 | ||
|
|
5fdb52388a | ||
|
|
799dc9a1ca | ||
|
|
2bb56e8a4b | ||
|
|
084d2bf692 | ||
|
|
10640c7561 | ||
|
|
9f044c48fe | ||
|
|
5567e1b7f2 | ||
|
|
3cd18d1e16 | ||
|
|
9d3e42b5d4 | ||
|
|
0f318c26c2 | ||
|
|
79f4e69a05 | ||
|
|
f13148af3d | ||
|
|
6598d23ff0 | ||
|
|
c83436655c | ||
|
|
62595c519c | ||
|
|
d2eb2d2941 | ||
|
|
1d12bebce4 | ||
|
|
942c0ee113 | ||
|
|
fbd3a38587 | ||
|
|
45b660985e | ||
|
|
0888125899 | ||
|
|
c85d9bcd84 | ||
|
|
dbf8af1146 | ||
|
|
d7ccf1454e | ||
|
|
652d447f8b | ||
|
|
8e91640084 | ||
|
|
0ca2891166 | ||
|
|
5000bb8db3 | ||
|
|
9927424826 | ||
|
|
2f58379feb | ||
|
|
c14d3f4274 | ||
|
|
d4673a2f07 | ||
|
|
3a0c94577f | ||
|
|
5a4e33e503 | ||
|
|
5649799167 | ||
|
|
c407b73c22 | ||
|
|
361add3385 | ||
|
|
9d6ab69d37 | ||
|
|
0f6da35c0b | ||
|
|
ad3f5de99a | ||
|
|
2de7ba0d0c | ||
|
|
b699088dd6 | ||
|
|
458c070004 | ||
|
|
babac6df3c | ||
|
|
f58477931f | ||
|
|
2171d491a6 | ||
|
|
aa911f88f2 | ||
|
|
bdbcaeff67 | ||
|
|
b2756b3c63 | ||
|
|
27f11ab583 | ||
|
|
2776970317 | ||
|
|
9d28bf7e82 | ||
|
|
6455b00742 | ||
|
|
16179a3b50 | ||
|
|
6a37c9d076 | ||
|
|
1915b1c00a | ||
|
|
a9982d6e28 | ||
|
|
1daeb8fe93 | ||
|
|
3dfb158382 | ||
|
|
fb7d247fa7 | ||
|
|
6bf2312a94 | ||
|
|
0cdcb83a7a | ||
|
|
e4f48e81fc | ||
|
|
1d32a95a09 | ||
|
|
4c934a78a6 | ||
|
|
c47bc86d37 | ||
|
|
a125781312 | ||
|
|
dfa951e574 | ||
|
|
76779e6f95 | ||
|
|
e9a79a32da | ||
|
|
967170a7b2 | ||
|
|
3326784315 | ||
|
|
fc553e1009 | ||
|
|
da172ff9b5 | ||
|
|
fc422853ef | ||
|
|
2852c07ec7 | ||
|
|
ead1c9ecab | ||
|
|
5b5066577f | ||
|
|
4af0bb3943 | ||
|
|
f2eaa79318 | ||
|
|
2ee7ce5829 | ||
|
|
0d7c94e7e9 | ||
|
|
9e29821012 | ||
|
|
38c307d6f1 | ||
|
|
520567793a | ||
|
|
e0fb379511 | ||
|
|
ba9362ccb2 | ||
|
|
261a36c435 | ||
|
|
cb92e46f8d | ||
|
|
126186041e | ||
|
|
6379e1703c | ||
|
|
2b246e431b | ||
|
|
526fcabffe | ||
|
|
75ff31f0cf | ||
|
|
46dab6e474 | ||
|
|
c7e8c07d40 | ||
|
|
932d2b77dc | ||
|
|
049de84cbb | ||
|
|
3bd44ea7ef | ||
|
|
317ccccfc6 | ||
|
|
220da6b58e | ||
|
|
6a7750d354 | ||
|
|
529803f791 | ||
|
|
4c23ab5664 | ||
|
|
e3c28fd0ec | ||
|
|
56ab61c29c | ||
|
|
d3056ba843 | ||
|
|
e34e2ec1f1 | ||
|
|
524bb5e4b7 | ||
|
|
3f8ea7764e | ||
|
|
f0d1e6936e | ||
|
|
9a21eec1b9 | ||
|
|
1703346bb6 | ||
|
|
b93d8e73a2 | ||
|
|
17c9813c98 | ||
|
|
e5ebe20a20 | ||
|
|
b5861dae39 | ||
|
|
54a03fd0d3 | ||
|
|
e8affcfde9 | ||
|
|
d376947a91 | ||
|
|
59e38fbdb0 | ||
|
|
492449b7c5 | ||
|
|
7cd21636d6 | ||
|
|
6ff49589be | ||
|
|
c950806541 | ||
|
|
3dcc12042f | ||
|
|
92925648e6 | ||
|
|
811c492bce | ||
|
|
73fa2e19e4 | ||
|
|
921e1af72b | ||
|
|
cc905da630 | ||
|
|
74bbfce8a0 | ||
|
|
8b67a0423d | ||
|
|
f1d527aa9c | ||
|
|
9e45d4d227 | ||
|
|
2dd0424d8f | ||
|
|
838f25b9db | ||
|
|
8809469f8e | ||
|
|
289f138c2a | ||
|
|
3d0dd60f56 | ||
|
|
9bb9a914ac | ||
|
|
44cef9999c | ||
|
|
3a792a021c | ||
|
|
2e5c63cfb9 | ||
|
|
9845363349 | ||
|
|
1a6fa7a799 | ||
|
|
6cd44662a8 | ||
|
|
9daf418886 | ||
|
|
37ee13353d | ||
|
|
8439e8871f | ||
|
|
4c1d3b4f3a | ||
|
|
cd3c66cb14 | ||
|
|
265b0114e4 | ||
|
|
17a63d599d | ||
|
|
d9e87fcd82 | ||
|
|
78c4cb11eb | ||
|
|
6feea75e45 | ||
|
|
2d1f7d0f33 | ||
|
|
841facc853 | ||
|
|
0e60bd3da7 | ||
|
|
5dc7f1ae2f | ||
|
|
6862cb4e58 | ||
|
|
0591530d44 | ||
|
|
592679538b | ||
|
|
9ef2699372 | ||
|
|
e4c37b916a | ||
|
|
7a8a0ae37e | ||
|
|
bd25097e44 | ||
|
|
3f140e818f | ||
|
|
dbba22131c | ||
|
|
8908828af0 | ||
|
|
20c9e1d406 | ||
|
|
741576526d | ||
|
|
b928ec112e | ||
|
|
559026b65c | ||
|
|
eb6fef63b3 | ||
|
|
1b767f8d26 | ||
|
|
3b061cda89 | ||
|
|
e0d7bb50f3 | ||
|
|
20e8e9167f | ||
|
|
268ede869d | ||
|
|
8b8ddaf31b | ||
|
|
f16fbeade7 | ||
|
|
d27677030d | ||
|
|
578e912faf | ||
|
|
bd8c6caa39 | ||
|
|
7635230c88 | ||
|
|
eb0d746082 | ||
|
|
61b3853390 | ||
|
|
76485cdd56 | ||
|
|
3d5401a8db | ||
|
|
6faecc2874 | ||
|
|
4650ca40c1 | ||
|
|
14ee26557a | ||
|
|
8e873013a9 | ||
|
|
b0caf46406 | ||
|
|
a0926c4064 | ||
|
|
b795b1c5ce | ||
|
|
61ba5f5c39 | ||
|
|
f177287fb6 | ||
|
|
ef929e78f3 | ||
|
|
274a55e257 | ||
|
|
9e24223085 | ||
|
|
3cd3d8bdcc | ||
|
|
eed3f2ff4c | ||
|
|
b09b4b1d17 | ||
|
|
cd9c667e8a | ||
|
|
2675e79dbd | ||
|
|
ef94f55c82 | ||
|
|
926919524b | ||
|
|
087f691544 | ||
|
|
3a81ebf0e2 | ||
|
|
3ffaaab8f3 | ||
|
|
5a98da2031 | ||
|
|
2e4014863f | ||
|
|
d3fcb42a8f | ||
|
|
51be153527 | ||
|
|
5728b7c8a8 | ||
|
|
71b6907c31 | ||
|
|
eead96ca26 | ||
|
|
f99e8770f0 | ||
|
|
f6ab59ceda | ||
|
|
8cda05c431 | ||
|
|
7af7ff92bf | ||
|
|
3169e6cdf4 | ||
|
|
f1004e2e36 | ||
|
|
4e88cbf318 | ||
|
|
413b121ce1 | ||
|
|
90dff3d1e1 | ||
|
|
3fc0b0a668 | ||
|
|
b5e53ec25c | ||
|
|
26eaec4c72 | ||
|
|
01a62d66cc | ||
|
|
f668e93f52 | ||
|
|
d0419edb92 | ||
|
|
c5eeb190d3 | ||
|
|
1d1e701ccb | ||
|
|
f38c7ae03a | ||
|
|
8f754142c7 | ||
|
|
3bd8f09c88 | ||
|
|
dd9cb21f8c | ||
|
|
2064cc88ab | ||
|
|
d982e35a17 | ||
|
|
4afcd44216 | ||
|
|
63252d3ee2 | ||
|
|
22a9502976 | ||
|
|
f972733426 | ||
|
|
5f9c21d00f |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @helloanoop @maintainer-bruno @lohit-bruno @naman-bruno
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: helloanoop
|
||||
4
.github/ISSUE_TEMPLATE/BugReport.yaml
vendored
4
.github/ISSUE_TEMPLATE/BugReport.yaml
vendored
@@ -27,7 +27,9 @@ body:
|
||||
required: false
|
||||
- label: annoying
|
||||
required: false
|
||||
|
||||
- label: this feature was working in a previous version but is broken in the current release.
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Bruno version
|
||||
|
||||
51
.github/workflows/tests.yml
vendored
51
.github/workflows/tests.yml
vendored
@@ -28,6 +28,11 @@ jobs:
|
||||
npm run build --workspace=packages/bruno-common
|
||||
npm run build --workspace=packages/bruno-query
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build --workspace=packages/bruno-converters
|
||||
npm run build --workspace=packages/bruno-requests
|
||||
|
||||
- name: Lint Check
|
||||
run: npm run lint
|
||||
|
||||
# tests
|
||||
- name: Test Package bruno-js
|
||||
@@ -45,6 +50,8 @@ jobs:
|
||||
run: npm run test --workspace=packages/bruno-app
|
||||
- name: Test Package bruno-common
|
||||
run: npm run test --workspace=packages/bruno-common
|
||||
- name: Test Package bruno-converters
|
||||
run: npm run test --workspace=packages/bruno-converters
|
||||
- name: Test Package bruno-electron
|
||||
run: npm run test --workspace=packages/bruno-electron
|
||||
|
||||
@@ -71,6 +78,8 @@ jobs:
|
||||
npm run build --workspace=packages/bruno-query
|
||||
npm run build --workspace=packages/bruno-common
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build --workspace=packages/bruno-converters
|
||||
npm run build --workspace=packages/bruno-requests
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
@@ -82,5 +91,47 @@ jobs:
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
if: always()
|
||||
with:
|
||||
check_name: CLI Test Results
|
||||
files: packages/bruno-tests/collection/junit.xml
|
||||
comment_mode: always
|
||||
e2e-test:
|
||||
name: Playwright E2E Tests
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: v22.11.x
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get --no-install-recommends install -y \
|
||||
libglib2.0-0 libnss3 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libgtk-3-0 libasound2t64 \
|
||||
xvfb
|
||||
npm ci --legacy-peer-deps
|
||||
sudo chown root /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
|
||||
sudo chmod 4755 /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
|
||||
|
||||
- name: Install dependencies for test collection environment
|
||||
run: |
|
||||
npm ci --prefix packages/bruno-tests/collection
|
||||
|
||||
- name: Build libraries
|
||||
run: |
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
xvfb-run npm run test:e2e
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -46,4 +46,8 @@ yarn-error.log*
|
||||
|
||||
#dev editor
|
||||
bruno.iml
|
||||
.idea
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Playwright
|
||||
/blob-report/
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
@@ -15,15 +15,15 @@
|
||||
| [正體中文](docs/contributing/contributing_zhtw.md)
|
||||
| [日本語](docs/contributing/contributing_ja.md)
|
||||
| [हिंदी](docs/contributing/contributing_hi.md)
|
||||
| [Nederlands](docs/contributing/contributing_nl.md)
|
||||
| [Dutch](docs/contributing/contributing_nl.md)
|
||||
|
||||
## Let's make Bruno better, together!!
|
||||
|
||||
We are happy that you are looking to improve Bruno. Below are the guidelines to get started bringing up Bruno on your computer.
|
||||
We are happy that you are looking to improve Bruno. Below are the guidelines to run Bruno on your computer.
|
||||
|
||||
### Technology Stack
|
||||
|
||||
Bruno is built using Next.js and React. We also use electron to ship a desktop version (that supports local collections)
|
||||
Bruno is built using React and Electron.
|
||||
|
||||
Libraries we use
|
||||
|
||||
@@ -37,38 +37,76 @@ Libraries we use
|
||||
- Filesystem Watcher - chokidar
|
||||
- i18n - i18next
|
||||
|
||||
### Dependencies
|
||||
|
||||
You would need [Node v20.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
|
||||
> [!IMPORTANT]
|
||||
> You would need [Node v22.x or the latest LTS version](https://nodejs.org/en/). We use npm workspaces in the project
|
||||
|
||||
## Development
|
||||
|
||||
Bruno is being developed as a desktop app. You need to load the app by running the Next.js app in one terminal and then run the electron app in another terminal.
|
||||
Bruno is a desktop app. Below are the instructions to run Bruno.
|
||||
|
||||
### Local Development
|
||||
> Note: We use React for the frontend and rsbuild for build and dev server.
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
```bash
|
||||
# use nodejs 20 version
|
||||
# use nodejs 22 version
|
||||
nvm use
|
||||
|
||||
# install deps
|
||||
npm i --legacy-peer-deps
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
#### Build packages
|
||||
|
||||
##### Option 1
|
||||
|
||||
```bash
|
||||
# build packages
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# bundle js sandbox libraries
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
```
|
||||
##### Option 2
|
||||
|
||||
# run next app (terminal 1)
|
||||
```bash
|
||||
# install dependencies and setup
|
||||
npm run setup
|
||||
```
|
||||
|
||||
#### Run the app
|
||||
|
||||
##### Option 1
|
||||
|
||||
```bash
|
||||
# run react app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
# run electron app (terminal 2)
|
||||
npm run dev:electron
|
||||
```
|
||||
|
||||
##### Option 2
|
||||
```bash
|
||||
# run electron and react app concurrently
|
||||
npm run dev
|
||||
```
|
||||
|
||||
#### Customize Electron `userData` path
|
||||
If `ELECTRON_USER_DATA_PATH` env-variable is present and its development mode, then `userData` path is modified accordingly.
|
||||
|
||||
e.g.
|
||||
```sh
|
||||
ELECTRON_USER_DATA_PATH=$(realpath ~/Desktop/bruno-test) npm run dev:electron
|
||||
```
|
||||
This will create a `bruno-test` folder on your Desktop and use it as the `userData` path.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
|
||||
@@ -87,7 +125,28 @@ find . -type f -name "package-lock.json" -delete
|
||||
|
||||
```bash
|
||||
# run bruno-schema tests
|
||||
npm test --workspace=packages/bruno-schema
|
||||
npm run test --workspace=packages/bruno-schema
|
||||
|
||||
# run bruno-query tests
|
||||
npm run test --workspace=packages/bruno-query
|
||||
|
||||
# run bruno-common tests
|
||||
npm run test --workspace=packages/bruno-common
|
||||
|
||||
# run bruno-converters tests
|
||||
npm run test --workspace=packages/bruno-converters
|
||||
|
||||
# run bruno-app tests
|
||||
npm run test --workspace=packages/bruno-app
|
||||
|
||||
# run bruno-electron tests
|
||||
npm run test --workspace=packages/bruno-electron
|
||||
|
||||
# run bruno-lang tests
|
||||
npm run test --workspace=packages/bruno-lang
|
||||
|
||||
# run bruno-toml tests
|
||||
npm run test --workspace=packages/bruno-toml
|
||||
|
||||
# run tests over all workspaces
|
||||
npm test --workspaces --if-present
|
||||
|
||||
@@ -21,7 +21,7 @@ Bibliotheken die wir benutzen
|
||||
|
||||
### Abhängigkeiten
|
||||
|
||||
Du benötigst [Node v20.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
||||
Du benötigst [Node v22.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
||||
|
||||
### Lass uns coden
|
||||
|
||||
@@ -42,12 +42,12 @@ Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zue
|
||||
|
||||
### Abhängigkeiten
|
||||
|
||||
- NodeJS v18
|
||||
- NodeJS v22
|
||||
|
||||
### Lokales Entwickeln
|
||||
|
||||
```bash
|
||||
# use nodejs 18 version
|
||||
# use nodejs 22 version
|
||||
nvm use
|
||||
|
||||
# install deps
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
[Inglés](../../contributing.md)
|
||||
|
||||
## ¡Juntos, hagamos a Bruno mejor!
|
||||
|
||||
@@ -6,58 +6,111 @@ Estamos encantados de que quieras ayudar a mejorar Bruno. A continuación encont
|
||||
|
||||
### Tecnologías utilizadas
|
||||
|
||||
Bruno está construido con NextJs y React. También usamos electron para distribuir una versión de escritorio (que soporta colecciones locales).
|
||||
Bruno está construido con React y Electron
|
||||
|
||||
Librerías que utilizamos:
|
||||
|
||||
- CSS - Tailwind
|
||||
- Editores de código - Codemirror
|
||||
- CSS - Tailwind CSS
|
||||
- Editores de código - CodeMirror
|
||||
- Manejo del estado - Redux
|
||||
- Íconos - Tabler Icons
|
||||
- Formularios - formik
|
||||
- Validación de esquemas - Yup
|
||||
- Cliente de peticiones - axios
|
||||
- Monitor del sistema de archivos - chokidar
|
||||
- i18n (internacionalización) - i18next
|
||||
|
||||
### Dependencias
|
||||
|
||||
Necesitarás [Node v20.x o la última versión LTS](https://nodejs.org/es) y npm 8.x. Ten en cuenta que utilizamos espacios de trabajo de npm en el proyecto.
|
||||
> [!IMPORTANT]
|
||||
> Necesitarás [Node v22.x o la última versión LTS](https://nodejs.org/es/). Ten en cuenta que Bruno usa los espacios de trabajo de npm
|
||||
|
||||
## Desarrollo
|
||||
|
||||
Bruno está siendo desarrollado como una aplicación de escritorio. Para ejecutarlo, primero debes ejecutar la aplicación de nextjs en una terminal y luego ejecutar la aplicación de electron en otra terminal.
|
||||
Bruno es una aplicación de escritorio. A continuación se detallan las instrucciones paso a paso para ejecutar Bruno.
|
||||
|
||||
### Dependencias
|
||||
> Nota: Utilizamos React para el frontend y rsbuild para el servidor de desarrollo.
|
||||
|
||||
- NodeJS v18
|
||||
### Instalar dependencias
|
||||
|
||||
```bash
|
||||
# Use la versión 22.x o LTS (Soporte a Largo Plazo) de Node.js
|
||||
nvm use 22.11.0
|
||||
|
||||
# instalar las dependencias
|
||||
npm i --legacy-peer-deps
|
||||
```
|
||||
|
||||
> ¿Por qué `--legacy-peer-deps`?: Fuerza la instalación ignorando conflictos en dependencias “peer”, evitando errores de árbol de dependencias.
|
||||
|
||||
### Desarrollo local
|
||||
|
||||
#### Construir paquetes
|
||||
|
||||
##### Opción 1
|
||||
|
||||
```bash
|
||||
# Utiliza la versión 18 de nodejs
|
||||
nvm use
|
||||
|
||||
# Instala las dependencias
|
||||
npm i --legacy-peer-deps
|
||||
|
||||
# Construye la documentación de graphql
|
||||
# construir paquetes
|
||||
npm run build:graphql-docs
|
||||
|
||||
# Construye bruno-query
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# Ejecuta la aplicación de nextjs (terminal 1)
|
||||
# empaquetar bibliotecas JavaScript del entorno de pruebas aislado
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
```
|
||||
|
||||
##### Opción 2
|
||||
|
||||
```bash
|
||||
# instalar dependencias y configurar el entorno
|
||||
npm run setup
|
||||
```
|
||||
|
||||
#### Ejecutar la aplicación
|
||||
|
||||
```bash
|
||||
# ejecutar aplicación react (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
# Ejecuta la aplicación de electron (terminal 2)
|
||||
# ejecutar aplicación electron (terminal 2)
|
||||
npm run dev:electron
|
||||
```
|
||||
|
||||
##### Opción 1
|
||||
|
||||
```bash
|
||||
# ejecutar aplicación react (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
# ejecutar aplicación electron (terminal 2)
|
||||
npm run dev:electron
|
||||
```
|
||||
|
||||
##### Opción 2
|
||||
|
||||
```bash
|
||||
# ejecutar aplicación electron y react de forma concurrente
|
||||
npm run dev
|
||||
```
|
||||
|
||||
#### Personalizar la ruta `userData` de Electron
|
||||
|
||||
Si la variable de entorno `ELECTRON_USER_DATA_PATH` está presente y se encuentra en modo de desarrollo, entonces la ruta `userData` se modifica en consecuencia.
|
||||
ejemplo:
|
||||
|
||||
```sh
|
||||
ELECTRON_USER_DATA_PATH=$(realpath ~/Desktop/bruno-test) npm run dev:electron
|
||||
```
|
||||
|
||||
Esto creará una carpeta llamada `bruno-test` en tu escritorio y la usará como la ruta userData.
|
||||
|
||||
### Solución de problemas
|
||||
|
||||
Es posible que encuentres un error de `Unsupported platform` cuando ejecutes `npm install`. Para solucionarlo, debes eliminar la carpeta `node_modules` y el archivo `package-lock.json`, luego, ejecuta `npm install`. Lo anterior debería instalar todos los paquetes necesarios para ejecutar la aplicación.
|
||||
Es posible que te encuentres con un error `Unsupported platform` cuando ejecutes `npm install`. Para solucionarlo, tendrás que eliminar las carpetas `node_modules` y el archivo `package-lock.json`, y luego volver a ejecutar `npm install`. Esto debería instalar todos los paquetes necesarios para que la aplicación funcione.
|
||||
|
||||
```shell
|
||||
```sh
|
||||
# Elimina la carpeta node_modules en los subdirectorios
|
||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||
rm -rf "$dir"
|
||||
@@ -69,10 +122,42 @@ find . -type f -name "package-lock.json" -delete
|
||||
|
||||
### Pruebas
|
||||
|
||||
```bash
|
||||
# ejecutar pruebas de esquema bruno
|
||||
npm test --workspace=packages/bruno-schema
|
||||
#### Pruebas individuales
|
||||
|
||||
```bash
|
||||
# ejecutar pruebas de bruno-app
|
||||
npm run test --workspace=packages/bruno-app
|
||||
|
||||
# ejecutar pruebas de bruno-electron
|
||||
npm run test --workspace=packages/bruno-electron
|
||||
|
||||
# ejecutar pruebas de bruno-cli
|
||||
npm run test --workspace=packages/bruno-cli
|
||||
|
||||
# ejecutar pruebas de bruno-common
|
||||
npm run test --workspace=packages/bruno-common
|
||||
|
||||
# ejecutar pruebas de bruno-converters
|
||||
npm run test --workspace=packages/bruno-converters
|
||||
|
||||
# ejecutar pruebas de bruno-schema
|
||||
npm run test --workspace=packages/bruno-schema
|
||||
|
||||
# ejecutar pruebas de bruno-query
|
||||
npm run test --workspace=packages/bruno-query
|
||||
|
||||
# ejecutar pruebas de bruno-js
|
||||
npm run test --workspace=packages/bruno-js
|
||||
|
||||
# ejecutar pruebas de bruno-lang
|
||||
npm run test --workspace=packages/bruno-lang
|
||||
|
||||
# ejecutar pruebas de bruno-toml
|
||||
npm run test --workspace=packages/bruno-toml
|
||||
```
|
||||
#### Pruebas en conjunto
|
||||
|
||||
```bash
|
||||
# ejecutar pruebas en todos los espacios de trabajo
|
||||
npm test --workspaces --if-present
|
||||
```
|
||||
|
||||
@@ -40,6 +40,8 @@ npm i --legacy-peer-deps
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# Next.js ऐप चलाएँ (टर्मिनल 1 पर)
|
||||
npm run dev:web
|
||||
|
||||
@@ -40,6 +40,8 @@ npm i --legacy-peer-deps
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# run next app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
@@ -40,6 +40,8 @@ npm i --legacy-peer-deps
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# next 앱 실행 (1번 터미널)
|
||||
npm run dev:web
|
||||
|
||||
@@ -40,6 +40,8 @@ npm i --legacy-peer-deps
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# draai next app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
@@ -42,6 +42,8 @@ npm i --legacy-peer-deps
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
|
||||
# spustite ďalšiu aplikáciu (terminál 1)
|
||||
npm run dev:web
|
||||
|
||||
@@ -74,12 +74,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# على نظام Linux عبر Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### التشغيل عبر منصات متعددة 🖥️
|
||||
|
||||
@@ -59,12 +59,11 @@ snap install bruno
|
||||
|
||||
# Apt এর মাধ্যমে লিনাক্সে
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### একাধিক প্ল্যাটফর্মে চালান 🖥️
|
||||
|
||||
@@ -63,12 +63,11 @@ snap install bruno
|
||||
|
||||
# 在 Linux 上用 Apt 安装
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### 在 Mac 上通过 Homebrew 安装 🖥️
|
||||
|
||||
@@ -78,12 +78,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# Auf Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Einsatz auf verschiedensten Plattformen 🖥️
|
||||
|
||||
@@ -75,12 +75,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# En Linux con Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Ejecútalo en múltiples plataformas 🖥️
|
||||
|
||||
@@ -63,12 +63,11 @@ snap install bruno
|
||||
|
||||
# Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Fonctionne sur de multiples plateformes 🖥️
|
||||
|
||||
151
docs/readme/readme_hi.md
Normal file
151
docs/readme/readme_hi.md
Normal file
@@ -0,0 +1,151 @@
|
||||
<br />
|
||||
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||
|
||||
### ब्रूनो - API इंटरफेस (API) का अन्वेषण और परीक्षण करने के लिए एक ओपन-सोर्स विकास वातावरण।
|
||||
|
||||
[](https://badge.fury.io/gh/usebruno%2Fbruno)
|
||||
[](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
|
||||
[](https://github.com/usebruno/bruno/pulse)
|
||||
[](https://twitter.com/use_bruno)
|
||||
[](https://www.usebruno.com)
|
||||
[](https://www.usebruno.com/downloads)
|
||||
|
||||
[English](../../readme.md)
|
||||
| [Українська](./readme_ua.md)
|
||||
| [Русский](./readme_ru.md)
|
||||
| [Türkçe](./readme_tr.md)
|
||||
| [Deutsch](./readme_de.md)
|
||||
| [Français](./readme_fr.md)
|
||||
| [Português (BR)](./readme_pt_br.md)
|
||||
| [한국어](./readme_kr.md)
|
||||
| [বাংলা](./readme_bn.md)
|
||||
| [Español](./readme_es.md)
|
||||
| [Italiano](./readme_it.md)
|
||||
| [Română](./readme_ro.md)
|
||||
| [Polski](./readme_pl.md)
|
||||
| [简体中文](./readme_cn.md)
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
| **हिन्दी**
|
||||
|
||||
ब्रूनो एक नया और अभिनव API क्लाइंट है, जिसका उद्देश्य Postman और अन्य समान उपकरणों द्वारा प्रस्तुत स्थिति को बदलना है।
|
||||
|
||||
ब्रूनो आपकी कलेक्शनों को सीधे आपकी फाइल सिस्टम के एक फ़ोल्डर में संग्रहीत करता है। हम API अनुरोधों के बारे में जानकारी सहेजने के लिए एक सामान्य टेक्स्ट मार्कअप भाषा, Bru, का उपयोग करते हैं।
|
||||
|
||||
आप अपनी API कलेक्शनों पर सहयोग करने के लिए Git या अपनी पसंद के किसी भी संस्करण नियंत्रण प्रणाली का उपयोग कर सकते हैं।
|
||||
|
||||
ब्रूनो केवल ऑफ़लाइन उपयोग के लिए है। ब्रूनो में कभी भी क्लाउड-सिंक जोड़ने की कोई योजना नहीं है। हम आपके डेटा की गोपनीयता को महत्व देते हैं और मानते हैं कि इसे आपके डिवाइस पर ही रहना चाहिए। हमारी दीर्घकालिक दृष्टि [यहाँ](https://github.com/usebruno/bruno/discussions/269) पढ़ें।
|
||||
|
||||
📢 हमारे हालिया India FOSS 3.0 सम्मेलन में हमारे वार्तालाप को [यहाँ](https://www.youtube.com/watch?v=7bSMFpbcPiY) देखें।
|
||||
|
||||
 <br /><br />
|
||||
|
||||
### गोल्डन संस्करण ✨
|
||||
|
||||
हमारी अधिकांश सुविधाएँ मुफ्त और ओपन-सोर्स हैं।
|
||||
हम [पारदर्शिता और स्थिरता के सिद्धांतों](https://github.com/usebruno/bruno/discussions/269) के बीच एक सामंजस्यपूर्ण संतुलन प्राप्त करने का प्रयास करते हैं।
|
||||
|
||||
[गोल्डन संस्करण](https://www.usebruno.com/pricing) के लिए खरीदारी जल्द ही $9 की कीमत पर उपलब्ध होगी! <br/>
|
||||
[यहाँ सदस्यता लें](https://usebruno.ck.page/4c65576bd4) ताकि आपको लॉन्च पर सूचनाएं मिलें।
|
||||
|
||||
### स्थापना
|
||||
|
||||
ब्रूनो Mac, Windows और Linux के लिए हमारे [वेबसाइट](https://www.usebruno.com/downloads) पर एक बाइनरी डाउनलोड के रूप में उपलब्ध है।
|
||||
|
||||
आप ब्रूनो को Homebrew, Chocolatey, Scoop, Snap, Flatpak और Apt जैसे पैकेज प्रबंधकों के माध्यम से भी स्थापित कर सकते हैं।
|
||||
|
||||
```sh
|
||||
# Mac पर Homebrew के माध्यम से
|
||||
brew install bruno
|
||||
|
||||
# Windows पर Chocolatey के माध्यम से
|
||||
choco install bruno
|
||||
|
||||
# Windows पर Scoop के माध्यम से
|
||||
scoop bucket add extras
|
||||
scoop install bruno
|
||||
|
||||
# Linux पर Snap के माध्यम से
|
||||
snap install bruno
|
||||
|
||||
# Linux पर Flatpak के माध्यम से
|
||||
flatpak install com.usebruno.Bruno
|
||||
|
||||
# Linux पर Apt के माध्यम से
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
|
||||
कई प्लेटफार्मों पर चलाएं 🖥️
|
||||
<br /><br />
|
||||
|
||||
Git के माध्यम से सहयोग करें 👩💻🧑💻
|
||||
या अपनी पसंद के किसी भी संस्करण नियंत्रण प्रणाली का उपयोग करें
|
||||
|
||||
<br /><br />
|
||||
|
||||
महत्वपूर्ण लिंक 📌
|
||||
हमारी दीर्घकालिक दृष्टि
|
||||
|
||||
रोडमैप
|
||||
|
||||
प्रलेखन
|
||||
|
||||
Stack Overflow
|
||||
|
||||
वेबसाइट
|
||||
|
||||
मूल्य निर्धारण
|
||||
|
||||
डाउनलोड
|
||||
|
||||
GitHub प्रायोजक
|
||||
|
||||
प्रस्तुतियाँ 🎥
|
||||
प्रशंसापत्र
|
||||
|
||||
ज्ञान केंद्र
|
||||
|
||||
Scriptmania
|
||||
|
||||
समर्थन ❤️
|
||||
यदि आप ब्रूनो को पसंद करते हैं और हमारे ओपन-सोर्स कार्य का समर्थन करना चाहते हैं, तो कृपया GitHub प्रायोजक के माध्यम से हमें प्रायोजित करने पर विचार करें।
|
||||
|
||||
प्रशंसापत्र साझा करें 📣
|
||||
यदि ब्रूनो ने आपके और आपकी टीमों के लिए काम में मदद की है, तो कृपया हमारे GitHub चर्चा में अपने प्रशंसापत्र साझा करना न भूलें
|
||||
|
||||
नए पैकेज प्रबंधकों में प्रकाशित करना
|
||||
अधिक जानकारी के लिए कृपया यहाँ देखें।
|
||||
|
||||
हमसे संपर्क करें 🌐
|
||||
𝕏 (ट्विटर) <br />
|
||||
वेबसाइट <br />
|
||||
डिस्कॉर्ड <br />
|
||||
लिंक्डइन
|
||||
|
||||
ट्रेडमार्क
|
||||
नाम
|
||||
|
||||
ब्रूनो एक ट्रेडमार्क है जो अनूप एम डी के स्वामित्व में है।
|
||||
|
||||
लोगो
|
||||
|
||||
लोगो OpenMoji से लिया गया है। लाइसेंस: CC BY-SA 4.0
|
||||
|
||||
योगदान 👩💻🧑💻
|
||||
हमें खुशी है कि आप ब्रूनो को बेहतर बनाने में रुचि रखते हैं। कृपया योगदान गाइड देखें।
|
||||
|
||||
यदि आप सीधे कोड के माध्यम से योगदान नहीं कर सकते, तो भी कृपया बग्स की रिपोर्ट करने और उन सुविधाओं का अनुरोध करने में संकोच न करें जिन्हें आपकी स्थिति को हल करने के लिए लागू किया जाना चाहिए।
|
||||
|
||||
लेखक
|
||||
<div align="center"> <a href="https://github.com/usebruno/bruno/graphs/contributors"> <img src="https://contrib.rocks/image?repo=usebruno/bruno" /> </a> </div>
|
||||
|
||||
लाइसेंस 📄
|
||||
MIT
|
||||
|
||||
@@ -59,12 +59,11 @@ snap install bruno
|
||||
|
||||
# Su Linux tramite Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Funziona su diverse piattaforme 🖥️
|
||||
|
||||
@@ -78,12 +78,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# LinuxでAptを使ってインストール
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### マルチプラットフォームでの実行に対応 🖥️
|
||||
|
||||
@@ -59,12 +59,11 @@ snap install bruno
|
||||
|
||||
# On Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### 여러 플랫폼에서 실행하세요. 🖥️
|
||||
|
||||
@@ -69,12 +69,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# On Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Uruchom na wielu platformach 🖥️
|
||||
|
||||
@@ -76,12 +76,11 @@ flatpak install com.usebruno.Bruno
|
||||
|
||||
# No Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Execute em várias plataformas 🖥️
|
||||
|
||||
@@ -59,12 +59,11 @@ snap install bruno
|
||||
|
||||
# Pe Linux cu Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Utilizați pe mai multe platforme 🖥️
|
||||
|
||||
@@ -63,12 +63,11 @@ snap install bruno
|
||||
|
||||
# Apt aracılığıyla Linux'ta
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### Birden fazla platformda çalıştırın 🖥️
|
||||
|
||||
@@ -63,12 +63,11 @@ snap install bruno
|
||||
|
||||
# 在 Linux 上使用 Apt 安裝
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo apt update && sudo apt install gpg
|
||||
sudo gpg --list-keys
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
sudo apt update && sudo apt install bruno
|
||||
```
|
||||
|
||||
### 跨多個平台運行 🖥️
|
||||
|
||||
5
e2e-tests/001-sanity-tests/001-home-screen.spec.ts
Normal file
5
e2e-tests/001-sanity-tests/001-home-screen.spec.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
|
||||
test('Check if the logo on top left is visible', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: 'bruno' })).toBeVisible();
|
||||
});
|
||||
31
e2e-tests/001-sanity-tests/002-create-new-collection.spec.ts
Normal file
31
e2e-tests/001-sanity-tests/002-create-new-collection.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
|
||||
test('Create new collection and add a simple HTTP request', async ({ page, createTmpDir }) => {
|
||||
await page.getByLabel('Create Collection').click();
|
||||
await page.getByLabel('Name').click();
|
||||
await page.getByLabel('Name').fill('test-collection');
|
||||
await page.getByLabel('Name').press('Tab');
|
||||
await page.getByLabel('Location').fill(await createTmpDir('test-collection'));
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await page.getByText('test-collection').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.locator('#create-new-tab').getByRole('img').click();
|
||||
await page.getByPlaceholder('Request Name').fill('r1');
|
||||
await page.getByPlaceholder('Request URL').click();
|
||||
await page.getByPlaceholder('Request URL').fill('http://localhost:8081');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
await page.locator('pre').filter({ hasText: 'http://localhost:' }).click();
|
||||
await page.locator('textarea').fill('/ping');
|
||||
await page.locator('#send-request').getByRole('img').nth(2).click();
|
||||
|
||||
await expect(page.getByRole('main')).toContainText('200 OK');
|
||||
|
||||
await page.getByRole('tab', { name: 'GET r1' }).locator('circle').click();
|
||||
await page.getByRole('button', { name: 'Save', exact: true }).click();
|
||||
await page.getByText('GETr1').click();
|
||||
await page.getByRole('button', { name: 'Clear response' }).click();
|
||||
await page.locator('body').press('ControlOrMeta+Enter');
|
||||
|
||||
await expect(page.getByRole('main')).toContainText('200 OK');
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/packages/bruno-tests/collection"]
|
||||
}
|
||||
49
e2e-tests/bruno-testbench/run-testbench-requests.spec.ts
Normal file
49
e2e-tests/bruno-testbench/run-testbench-requests.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
|
||||
test.describe.parallel('Run Testbench Requests', () => {
|
||||
test('Run bruno-testbench in Developer Mode', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
await page.getByText('bruno-testbench').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.locator('.environment-selector').nth(1).click();
|
||||
await page.locator('.dropdown-item').getByText('Prod').click();
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const [totalRequests, passed, failed, skipped] = result
|
||||
.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/)
|
||||
.slice(1);
|
||||
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test.fixme('Run bruno-testbench in Safe Mode', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
await page.getByText('bruno-testbench').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await page.locator('.environment-selector').nth(1).click();
|
||||
await page.locator('.dropdown-item').getByText('Prod').click();
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const [totalRequests, passed, failed, skipped] = result
|
||||
.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/)
|
||||
.slice(1);
|
||||
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
|
||||
test('Should verify all support links with correct URL in preference > Support tab', async ({ page }) => {
|
||||
|
||||
// Open Preferences
|
||||
await page.getByLabel('Open Preferences').click();
|
||||
|
||||
// Verify Support tab
|
||||
await page.getByRole('tab', { name: 'Support' }).click();
|
||||
|
||||
const locator_twitter = page.getByRole('link', { name: 'Twitter' });
|
||||
expect(await locator_twitter.getAttribute('href')).toEqual('https://twitter.com/use_bruno');
|
||||
|
||||
const locator_github = page.getByRole('link', { name: 'GitHub', exact: true });
|
||||
expect(await locator_github.getAttribute('href')).toEqual('https://github.com/usebruno/bruno');
|
||||
|
||||
const locator_discord = page.getByRole('link', { name: 'Discord', exact: true });
|
||||
expect(await locator_discord.getAttribute('href')).toEqual('https://discord.com/invite/KgcZUncpjq');
|
||||
|
||||
const locator_reportissues = page.getByRole('link', { name: 'Report Issues', exact: true });
|
||||
expect(await locator_reportissues.getAttribute('href')).toEqual('https://github.com/usebruno/bruno/issues');
|
||||
|
||||
const locator_documentation = page.getByRole('link', { name: 'Documentation', exact: true });
|
||||
expect(await locator_documentation.getAttribute('href')).toEqual('https://docs.usebruno.com');
|
||||
|
||||
|
||||
});
|
||||
54
eslint.config.js
Normal file
54
eslint.config.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// eslint.config.js
|
||||
const { defineConfig } = require("eslint/config");
|
||||
const globals = require("globals");
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
files: ["packages/bruno-app/**/*.{js,jsx,ts}"],
|
||||
ignores: ["**/*.config.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jest,
|
||||
global: false,
|
||||
require: false,
|
||||
Buffer: false,
|
||||
process: false
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
// It prevents lint errors when using CommonJS exports (module.exports) in Jest mocks.
|
||||
files: ["packages/bruno-app/src/test-utils/mocks/codemirror.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-electron/**/*.{js}"],
|
||||
ignores: ["**/*.config.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
}
|
||||
]);
|
||||
5500
package-lock.json
generated
5500
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -6,24 +6,31 @@
|
||||
"packages/bruno-electron",
|
||||
"packages/bruno-cli",
|
||||
"packages/bruno-common",
|
||||
"packages/bruno-converters",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-query",
|
||||
"packages/bruno-js",
|
||||
"packages/bruno-lang",
|
||||
"packages/bruno-tests",
|
||||
"packages/bruno-toml",
|
||||
"packages/bruno-graphql-docs"
|
||||
"packages/bruno-graphql-docs",
|
||||
"packages/bruno-requests"
|
||||
],
|
||||
"homepage": "https://usebruno.com",
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@jest/globals": "^29.2.0",
|
||||
"@playwright/test": "^1.27.1",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^9.26.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"husky": "^8.0.3",
|
||||
"globals": "^16.1.0",
|
||||
"jest": "^29.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"playwright": "^1.51.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"randomstring": "^1.2.2",
|
||||
"rimraf": "^6.0.1",
|
||||
@@ -31,13 +38,17 @@
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "node ./scripts/setup.js",
|
||||
"watch:converters": "npm run watch --workspace=packages/bruno-converters",
|
||||
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
|
||||
"dev:watch": "node ./scripts/dev-hot-reload.js",
|
||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
|
||||
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
|
||||
"build:bruno-requests": "npm run build --workspace=packages/bruno-requests",
|
||||
"build:bruno-converters": "npm run build --workspace=packages/bruno-converters",
|
||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||
"build:electron": "node ./scripts/build-electron.js",
|
||||
@@ -47,12 +58,18 @@
|
||||
"build:electron:deb": "./scripts/build-electron.sh deb",
|
||||
"build:electron:rpm": "./scripts/build-electron.sh rpm",
|
||||
"build:electron:snap": "./scripts/build-electron.sh snap",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:report": "npx playwright show-report",
|
||||
"watch:common": "npm run watch --workspace=packages/bruno-common",
|
||||
"test:codegen": "node playwright/codegen.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||
"prepare": "husky install"
|
||||
"lint": "node --max_old_space_size=4096 $(npx which eslint)"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "3.29.5"
|
||||
"rollup": "3.29.5",
|
||||
"electron-store": {
|
||||
"conf": {
|
||||
"json-schema-typed": "8.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
||||
"plugins": [["styled-components", { "ssr": true }]]
|
||||
}
|
||||
9
packages/bruno-app/babel.config.js
Normal file
9
packages/bruno-app/babel.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
['@babel/preset-react', {
|
||||
runtime: 'automatic'
|
||||
}]
|
||||
],
|
||||
plugins: ['babel-plugin-styled-components']
|
||||
};
|
||||
@@ -1,5 +1,11 @@
|
||||
module.exports = {
|
||||
rootDir: '.',
|
||||
transform: {
|
||||
'^.+\\.[jt]sx?$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!strip-json-comments|nanoid|xml-formatter)/",
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||
@@ -8,9 +14,17 @@ module.exports = {
|
||||
'^api/(.*)$': '<rootDir>/src/api/$1',
|
||||
'^pageComponents/(.*)$': '<rootDir>/src/pageComponents/$1',
|
||||
'^providers/(.*)$': '<rootDir>/src/providers/$1',
|
||||
'^utils/(.*)$': '<rootDir>/src/utils/$1'
|
||||
'^utils/(.*)$': '<rootDir>/src/utils/$1',
|
||||
'^test-utils/(.*)$': '<rootDir>/src/test-utils/$1'
|
||||
},
|
||||
clearMocks: true,
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
testEnvironment: 'node'
|
||||
};
|
||||
testEnvironment: 'jsdom',
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom'],
|
||||
setupFiles: [
|
||||
'<rootDir>/jest.setup.js',
|
||||
],
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/*.spec.[jt]s?(x)'
|
||||
]
|
||||
};
|
||||
11
packages/bruno-app/jest.setup.js
Normal file
11
packages/bruno-app/jest.setup.js
Normal file
@@ -0,0 +1,11 @@
|
||||
jest.mock('nanoid', () => {
|
||||
return {
|
||||
nanoid: () => {}
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('strip-json-comments', () => {
|
||||
return {
|
||||
stripJsonComments: (str) => str
|
||||
};
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"assets/*": ["src/assets/*"],
|
||||
"ui/*": ["src/ui/*"],
|
||||
"components/*": ["src/components/*"],
|
||||
"hooks/*": ["src/hooks/*"],
|
||||
"themes/*": ["src/themes/*"],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@usebruno/app",
|
||||
"version": "1.39.0",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "rsbuild dev",
|
||||
@@ -11,7 +12,6 @@
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@fontsource/inter": "^5.0.15",
|
||||
"@prantlf/jsonlint": "^16.0.0",
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
@@ -73,28 +73,37 @@
|
||||
"react-tooltip": "^5.5.2",
|
||||
"sass": "^1.46.0",
|
||||
"semver": "^7.7.1",
|
||||
"shell-quote": "^1.8.3",
|
||||
"strip-json-comments": "^5.0.1",
|
||||
"styled-components": "^5.3.3",
|
||||
"system": "^2.0.1",
|
||||
"url": "^0.11.3",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@rsbuild/core": "^1.1.2",
|
||||
"@rsbuild/plugin-babel": "^1.0.3",
|
||||
"@rsbuild/plugin-node-polyfill": "^1.2.0",
|
||||
"@rsbuild/plugin-react": "^1.0.7",
|
||||
"@rsbuild/plugin-sass": "^1.1.0",
|
||||
"@rsbuild/plugin-styled-components": "1.1.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"babel-plugin-styled-components": "^2.1.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "7.1.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"postcss": "8.4.47",
|
||||
"style-loader": "^3.3.1",
|
||||
|
||||
@@ -19,7 +19,12 @@ export default defineConfig({
|
||||
})
|
||||
],
|
||||
source: {
|
||||
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file
|
||||
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file,
|
||||
exclude: [
|
||||
'**/test-utils/**',
|
||||
'**/*.test.*',
|
||||
'**/*.spec.*'
|
||||
]
|
||||
},
|
||||
html: {
|
||||
title: 'Bruno'
|
||||
@@ -34,6 +39,16 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
ignoreWarnings: [
|
||||
(warning) => warning.message.includes('Critical dependency: the request of a dependency is an expression') && warning?.moduleDescriptor?.name?.includes('flow-parser')
|
||||
],
|
||||
// Add externals configuration to exclude Node.js libraries
|
||||
externals: {
|
||||
// List specific Node.js modules you want to exclude
|
||||
// Format: 'module-name': 'commonjs module-name'
|
||||
'worker_threads': 'commonjs worker_threads',
|
||||
// 'path': 'commonjs path'
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
40
packages/bruno-app/src/components/BulkEditor/index.js
Normal file
40
packages/bruno-app/src/components/BulkEditor/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { parseBulkKeyValue, serializeBulkKeyValue } from 'utils/common/bulkKeyValueUtils';
|
||||
|
||||
const BulkEditor = ({ params, onChange, onToggle, onSave, onRun }) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const { displayedTheme } = useTheme();
|
||||
|
||||
const parsedParams = useMemo(() => serializeBulkKeyValue(params), [params]);
|
||||
|
||||
const handleEdit = (value) => {
|
||||
const parsed = parseBulkKeyValue(value);
|
||||
onChange(parsed);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-[200px]">
|
||||
<CodeEditor
|
||||
mode="text/plain"
|
||||
theme={displayedTheme}
|
||||
font={preferences.codeFont || 'default'}
|
||||
value={parsedParams}
|
||||
onEdit={handleEdit}
|
||||
onSave={onSave}
|
||||
onRun={onRun}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex btn-action justify-between items-center mt-3">
|
||||
<button className="text-link select-none ml-auto" onClick={onToggle}>
|
||||
Key/Value Edit
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkEditor;
|
||||
@@ -102,6 +102,13 @@ const StyledWrapper = styled.div`
|
||||
.cm-s-default span.cm-variable {
|
||||
color: #397d13 !important;
|
||||
}
|
||||
|
||||
//matching bracket fix
|
||||
.CodeMirror-matchingbracket {
|
||||
background: #5cc0b48c !important;
|
||||
text-decoration:unset;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -8,116 +8,19 @@
|
||||
import React from 'react';
|
||||
import { isEqual, escapeRegExp } from 'lodash';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import { setupAutoComplete } from 'utils/codemirror/autocomplete';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import * as jsonlint from '@prantlf/jsonlint';
|
||||
import { JSHINT } from 'jshint';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const CodeMirror = require('codemirror');
|
||||
window.jsonlint = jsonlint;
|
||||
window.JSHINT = JSHINT;
|
||||
|
||||
const TAB_SIZE = 2;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
window.jsonlint = jsonlint;
|
||||
window.JSHINT = JSHINT;
|
||||
//This should be done dynamically if possible
|
||||
const hintWords = [
|
||||
'res',
|
||||
'res.status',
|
||||
'res.statusText',
|
||||
'res.headers',
|
||||
'res.body',
|
||||
'res.responseTime',
|
||||
'res.getStatus()',
|
||||
'res.getStatusText()',
|
||||
'res.getHeader(name)',
|
||||
'res.getHeaders()',
|
||||
'res.getBody()',
|
||||
'res.getResponseTime()',
|
||||
'req',
|
||||
'req.url',
|
||||
'req.method',
|
||||
'req.headers',
|
||||
'req.body',
|
||||
'req.timeout',
|
||||
'req.getUrl()',
|
||||
'req.setUrl(url)',
|
||||
'req.getMethod()',
|
||||
'req.getAuthMode()',
|
||||
'req.setMethod(method)',
|
||||
'req.getHeader(name)',
|
||||
'req.getHeaders()',
|
||||
'req.setHeader(name, value)',
|
||||
'req.setHeaders(data)',
|
||||
'req.getBody()',
|
||||
'req.setBody(data)',
|
||||
'req.setMaxRedirects(maxRedirects)',
|
||||
'req.getTimeout()',
|
||||
'req.setTimeout(timeout)',
|
||||
'req.getExecutionMode()',
|
||||
'bru',
|
||||
'bru.cwd()',
|
||||
'bru.getEnvName()',
|
||||
'bru.getProcessEnv(key)',
|
||||
'bru.hasEnvVar(key)',
|
||||
'bru.getEnvVar(key)',
|
||||
'bru.getFolderVar(key)',
|
||||
'bru.getCollectionVar(key)',
|
||||
'bru.setEnvVar(key,value)',
|
||||
'bru.deleteEnvVar(key)',
|
||||
'bru.hasVar(key)',
|
||||
'bru.getVar(key)',
|
||||
'bru.setVar(key,value)',
|
||||
'bru.deleteVar(key)',
|
||||
'bru.deleteAllVars()',
|
||||
'bru.setNextRequest(requestName)',
|
||||
'req.disableParsingResponseJson()',
|
||||
'bru.getRequestVar(key)',
|
||||
'bru.runRequest(requestPathName)',
|
||||
'bru.getAssertionResults()',
|
||||
'bru.getTestResults()',
|
||||
'bru.sleep(ms)',
|
||||
'bru.getGlobalEnvVar(key)',
|
||||
'bru.setGlobalEnvVar(key, value)',
|
||||
'bru.runner',
|
||||
'bru.runner.setNextRequest(requestName)',
|
||||
'bru.runner.skipRequest()',
|
||||
'bru.runner.stopExecution()'
|
||||
];
|
||||
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
||||
const cursor = editor.getCursor();
|
||||
const currentLine = editor.getLine(cursor.line);
|
||||
let startBru = cursor.ch;
|
||||
let endBru = startBru;
|
||||
while (endBru < currentLine.length && /[\w.]/.test(currentLine.charAt(endBru))) ++endBru;
|
||||
while (startBru && /[\w.]/.test(currentLine.charAt(startBru - 1))) --startBru;
|
||||
let curWordBru = startBru != endBru && currentLine.slice(startBru, endBru);
|
||||
|
||||
let start = cursor.ch;
|
||||
let end = start;
|
||||
while (end < currentLine.length && /[\w]/.test(currentLine.charAt(end))) ++end;
|
||||
while (start && /[\w]/.test(currentLine.charAt(start - 1))) --start;
|
||||
const jsHinter = CodeMirror.hint.javascript;
|
||||
let result = jsHinter(editor) || { list: [] };
|
||||
result.to = CodeMirror.Pos(cursor.line, end);
|
||||
result.from = CodeMirror.Pos(cursor.line, start);
|
||||
if (curWordBru) {
|
||||
hintWords.forEach((h) => {
|
||||
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
|
||||
result.list.push(curWordBru.includes('.') ? h.split('.')?.at(-1) : h);
|
||||
}
|
||||
});
|
||||
result.list?.sort();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
CodeMirror.commands.autocomplete = (cm, hint, options) => {
|
||||
cm.showHint({ hint, ...options });
|
||||
};
|
||||
}
|
||||
|
||||
export default class CodeEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -137,12 +40,17 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const variables = getAllVariables(this.props.collection, this.props.item);
|
||||
|
||||
const editor = (this.editor = CodeMirror(this._node, {
|
||||
value: this.props.value || '',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
tabSize: TAB_SIZE,
|
||||
mode: this.props.mode || 'application/ld+json',
|
||||
brunoVarInfo: {
|
||||
variables
|
||||
},
|
||||
keyMap: 'sublime',
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
@@ -274,30 +182,24 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
if (editor) {
|
||||
editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false);
|
||||
editor.on('change', this._onEdit);
|
||||
this.addOverlay();
|
||||
}
|
||||
if (this.props.mode == 'javascript') {
|
||||
editor.on('keyup', function (cm, event) {
|
||||
const cursor = editor.getCursor();
|
||||
const currentLine = editor.getLine(cursor.line);
|
||||
let start = cursor.ch;
|
||||
let end = start;
|
||||
while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end;
|
||||
while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start;
|
||||
let curWord = start != end && currentLine.slice(start, end);
|
||||
// Qualify if autocomplete will be shown
|
||||
if (
|
||||
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Meta|Alt|Home|End\s)\w*/.test(event.key) &&
|
||||
curWord.length > 0 &&
|
||||
!/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) &&
|
||||
/(?<!\d)[a-zA-Z\._]$/.test(curWord)
|
||||
) {
|
||||
CodeMirror.commands.autocomplete(cm, CodeMirror.hint.brunoJS, { completeSingle: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Setup AutoComplete Helper for all modes
|
||||
const autoCompleteOptions = {
|
||||
showHintsFor: this.props.showHintsFor
|
||||
};
|
||||
|
||||
const getVariables = () => getAllVariables(this.props.collection, this.props.item);
|
||||
|
||||
this.brunoAutoCompleteCleanup = setupAutoComplete(
|
||||
editor,
|
||||
getVariables,
|
||||
autoCompleteOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,6 +240,9 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
this._unbindSearchHandler();
|
||||
if (this.brunoAutoCompleteCleanup) {
|
||||
this.brunoAutoCompleteCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -362,7 +267,7 @@ export default class CodeEditor extends React.Component {
|
||||
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||
this.variables = variables;
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode, false, this.props.enableVariableHighlighting);
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
||||
|
||||
51
packages/bruno-app/src/components/CodeEditor/index.spec.js
Normal file
51
packages/bruno-app/src/components/CodeEditor/index.spec.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { render, act } from '@testing-library/react';
|
||||
import CodeEditor from './index';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
jest.mock('codemirror', () => {
|
||||
const codemirror = require('test-utils/mocks/codemirror');
|
||||
return codemirror;
|
||||
});
|
||||
|
||||
const MOCK_THEME = {
|
||||
codemirror: {
|
||||
bg: "#1e1e1e",
|
||||
border: "#333",
|
||||
},
|
||||
textLink: "#007acc",
|
||||
};
|
||||
|
||||
const setupEditorState = (editor, { value, cursorPosition }) => {
|
||||
editor._currentValue = value;
|
||||
editor.getCursor.mockReturnValue({ line: 0, ch: cursorPosition });
|
||||
editor.getRange.mockImplementation((from, to) => {
|
||||
if (from.line === 0 && from.ch === 0 && to.line === 0 && to.ch === cursorPosition) {
|
||||
return value;
|
||||
}
|
||||
return editor._currentValue.slice(from.ch, to.ch);
|
||||
});
|
||||
|
||||
editor.state = {
|
||||
completionActive: null,
|
||||
}
|
||||
};
|
||||
|
||||
const setupEditorWithRef = () => {
|
||||
const ref = React.createRef();
|
||||
const { rerender } = render(
|
||||
<ThemeProvider theme={MOCK_THEME}>
|
||||
<CodeEditor ref={ref} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
return { ref, rerender };
|
||||
};
|
||||
|
||||
describe('CodeEditor', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it("add CodeEditor related tests here", () => {});
|
||||
});
|
||||
@@ -95,7 +95,7 @@ const AuthMode = ({ collection }) => {
|
||||
onModeChange('oauth2');
|
||||
}}
|
||||
>
|
||||
Oauth2
|
||||
OAuth 2.0
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
|
||||
@@ -21,12 +21,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -38,12 +38,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -55,12 +55,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -72,12 +72,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -89,12 +89,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -106,12 +106,12 @@ const AwsV4Auth = ({ collection }) => {
|
||||
mode: 'awsv4',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@ const BasicAuth = ({ collection }) => {
|
||||
mode: 'basic',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: basicAuth.password
|
||||
username: username || '',
|
||||
password: basicAuth.password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -34,8 +34,8 @@ const BasicAuth = ({ collection }) => {
|
||||
mode: 'basic',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: basicAuth.username,
|
||||
password: password
|
||||
username: basicAuth.username || '',
|
||||
password: password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@ const DigestAuth = ({ collection }) => {
|
||||
mode: 'digest',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: digestAuth.password
|
||||
username: username || '',
|
||||
password: digestAuth.password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -34,8 +34,8 @@ const DigestAuth = ({ collection }) => {
|
||||
mode: 'digest',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: digestAuth.username,
|
||||
password: password
|
||||
username: digestAuth.username || '',
|
||||
password: password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -28,9 +28,9 @@ const NTLMAuth = ({ collection }) => {
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: ntlmAuth.password,
|
||||
domain: ntlmAuth.domain
|
||||
username: username || '',
|
||||
password: ntlmAuth.password || '',
|
||||
domain: ntlmAuth.domain || ''
|
||||
|
||||
}
|
||||
})
|
||||
@@ -43,9 +43,9 @@ const NTLMAuth = ({ collection }) => {
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: password,
|
||||
domain: ntlmAuth.domain
|
||||
username: ntlmAuth.username || '',
|
||||
password: password || '',
|
||||
domain: ntlmAuth.domain || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -57,9 +57,9 @@ const NTLMAuth = ({ collection }) => {
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: ntlmAuth.password,
|
||||
domain: domain
|
||||
username: ntlmAuth.username || '',
|
||||
password: ntlmAuth.password || '',
|
||||
domain: domain || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
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 { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { clearOauth2Cache } from 'utils/network/index';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const OAuth2AuthorizationCode = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendCollectionOauth2Request(collection.uid));
|
||||
};
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, state, pkce } = oAuth;
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code',
|
||||
callbackUrl,
|
||||
authorizationUrl,
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
state,
|
||||
pkce,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handlePKCEToggle = (e) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code',
|
||||
callbackUrl,
|
||||
authorizationUrl,
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
state,
|
||||
pkce: !Boolean(oAuth?.['pkce'])
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearCache = (e) => {
|
||||
clearOauth2Cache(collection?.uid)
|
||||
.then(() => {
|
||||
toast.success('cleared cache successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex flex-row w-full gap-4" key="pkce">
|
||||
<label className="block font-medium">Use PKCE</label>
|
||||
<input
|
||||
className="cursor-pointer"
|
||||
type="checkbox"
|
||||
checked={Boolean(oAuth?.['pkce'])}
|
||||
onChange={handlePKCEToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4">
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Clear Cache
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2AuthorizationCode;
|
||||
@@ -1,33 +0,0 @@
|
||||
const inputsConfig = [
|
||||
{
|
||||
key: 'callbackUrl',
|
||||
label: 'Callback URL'
|
||||
},
|
||||
{
|
||||
key: 'authorizationUrl',
|
||||
label: 'Authorization URL'
|
||||
},
|
||||
{
|
||||
key: 'accessTokenUrl',
|
||||
label: 'Access Token URL'
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID'
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
label: 'Scope'
|
||||
},
|
||||
{
|
||||
key: 'state',
|
||||
label: 'State'
|
||||
}
|
||||
];
|
||||
|
||||
export { inputsConfig };
|
||||
@@ -1,16 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,70 +0,0 @@
|
||||
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 { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const OAuth2ClientCredentials = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendCollectionOauth2Request(collection.uid));
|
||||
};
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
const { accessTokenUrl, clientId, clientSecret, scope } = oAuth;
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType: 'client_credentials',
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2ClientCredentials;
|
||||
@@ -1,21 +0,0 @@
|
||||
const inputsConfig = [
|
||||
{
|
||||
key: 'accessTokenUrl',
|
||||
label: 'Access Token URL'
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID'
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
label: 'Scope'
|
||||
}
|
||||
];
|
||||
|
||||
export { inputsConfig };
|
||||
@@ -1,54 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
|
||||
.grant-type-mode-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grant-type-label {
|
||||
width: fit-content;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,98 +0,0 @@
|
||||
import React, { useRef, forwardRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { humanizeGrantType } from 'utils/collections';
|
||||
import { useEffect } from 'react';
|
||||
import { updateCollectionAuth, updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const GrantTypeSelector = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end grant-type-label select-none">
|
||||
{humanizeGrantType(oAuth?.grantType)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onGrantTypeChange = (grantType) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// initialize redux state with a default oauth2 grant type
|
||||
// authorization_code - default option
|
||||
!oAuth?.grantType &&
|
||||
dispatch(
|
||||
updateCollectionAuthMode({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
!oAuth?.grantType &&
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code'
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [oAuth]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<label className="block font-medium mb-2">Grant Type</label>
|
||||
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('password');
|
||||
}}
|
||||
>
|
||||
Password Credentials
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('authorization_code');
|
||||
}}
|
||||
>
|
||||
Authorization Code
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('client_credentials');
|
||||
}}
|
||||
>
|
||||
Client Credentials
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default GrantTypeSelector;
|
||||
@@ -1,16 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,72 +0,0 @@
|
||||
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 { saveCollectionRoot, sendCollectionOauth2Request } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendCollectionOauth2Request(collection.uid));
|
||||
};
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
const { accessTokenUrl, username, password, clientId, clientSecret, scope } = oAuth;
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
grantType: 'password',
|
||||
accessTokenUrl,
|
||||
username,
|
||||
password,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2AuthorizationCode;
|
||||
@@ -1,29 +0,0 @@
|
||||
const inputsConfig = [
|
||||
{
|
||||
key: 'accessTokenUrl',
|
||||
label: 'Access Token URL'
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username'
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password'
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID'
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
label: 'Scope'
|
||||
}
|
||||
];
|
||||
|
||||
export { inputsConfig };
|
||||
@@ -1,21 +1,33 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GrantTypeSelector from './GrantTypeSelector/index';
|
||||
import OAuth2PasswordCredentials from './PasswordCredentials/index';
|
||||
import OAuth2AuthorizationCode from './AuthorizationCode/index';
|
||||
import OAuth2ClientCredentials from './ClientCredentials/index';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import OAuth2AuthorizationCode from 'components/RequestPane/Auth/OAuth2/AuthorizationCode/index';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/PasswordCredentials/index';
|
||||
import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index';
|
||||
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
|
||||
|
||||
const GrantTypeComponentMap = ({collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const save = () => {
|
||||
dispatch(saveCollectionRoot(collection.uid));
|
||||
};
|
||||
|
||||
let request = collection.draft ? get(collection, 'draft.request', {}) : get(collection, 'root.request', {});
|
||||
const grantType = get(request, 'auth.oauth2.grantType', {});
|
||||
|
||||
const grantTypeComponentMap = (grantType, collection) => {
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
return <OAuth2PasswordCredentials collection={collection} />;
|
||||
return <OAuth2PasswordCredentials save={save} request={request} updateAuth={updateCollectionAuth} collection={collection} />;
|
||||
break;
|
||||
case 'authorization_code':
|
||||
return <OAuth2AuthorizationCode collection={collection} />;
|
||||
return <OAuth2AuthorizationCode save={save} request={request} updateAuth={updateCollectionAuth} collection={collection} />;
|
||||
break;
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials collection={collection} />;
|
||||
return <OAuth2ClientCredentials save={save} request={request} updateAuth={updateCollectionAuth} collection={collection} />;
|
||||
break;
|
||||
default:
|
||||
return <div>TBD</div>;
|
||||
@@ -24,12 +36,12 @@ const grantTypeComponentMap = (grantType, collection) => {
|
||||
};
|
||||
|
||||
const OAuth2 = ({ collection }) => {
|
||||
const oAuth = get(collection, 'root.request.auth.oauth2', {});
|
||||
let request = collection.draft ? get(collection, 'draft.request', {}) : get(collection, 'root.request', {});
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<GrantTypeSelector collection={collection} />
|
||||
{grantTypeComponentMap(oAuth?.grantType, collection)}
|
||||
<GrantTypeSelector request={request} updateAuth={updateCollectionAuth} collection={collection} />
|
||||
<GrantTypeComponentMap collection={collection} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,8 +21,8 @@ const WsseAuth = ({ collection }) => {
|
||||
mode: 'wsse',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username,
|
||||
password: wsseAuth.password
|
||||
username: username || '',
|
||||
password: wsseAuth.password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -34,8 +34,8 @@ const WsseAuth = ({ collection }) => {
|
||||
mode: 'wsse',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: wsseAuth.username,
|
||||
password
|
||||
username: wsseAuth.username || '',
|
||||
password: password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -25,7 +25,10 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
passphrase: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
domain: Yup.string().required(),
|
||||
domain: Yup.string()
|
||||
.required()
|
||||
.trim()
|
||||
.test('not-empty-after-trim', 'Domain is required', value => value && value.trim().length > 0),
|
||||
type: Yup.string().required().oneOf(['cert', 'pfx']),
|
||||
certFilePath: Yup.string().when('type', {
|
||||
is: (type) => type == 'cert',
|
||||
@@ -45,7 +48,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
let relevantValues = {};
|
||||
if (values.type === 'cert') {
|
||||
relevantValues = {
|
||||
domain: values.domain,
|
||||
domain: values.domain?.trim(),
|
||||
type: values.type,
|
||||
certFilePath: values.certFilePath,
|
||||
keyFilePath: values.keyFilePath,
|
||||
@@ -53,7 +56,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
};
|
||||
} else {
|
||||
relevantValues = {
|
||||
domain: values.domain,
|
||||
domain: values.domain?.trim(),
|
||||
type: values.type,
|
||||
pfxFilePath: values.pfxFilePath,
|
||||
passphrase: values.passphrase
|
||||
@@ -127,15 +130,20 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
<label className="settings-label" htmlFor="domain">
|
||||
Domain
|
||||
</label>
|
||||
<input
|
||||
id="domain"
|
||||
type="text"
|
||||
name="domain"
|
||||
placeholder="*.example.org"
|
||||
className="block textbox non-passphrase-input"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.domain || ''}
|
||||
/>
|
||||
<div className="relative flex items-center">
|
||||
<div className="absolute left-0 pl-2 text-gray-400 pointer-events-none flex items-center h-full">
|
||||
https://
|
||||
</div>
|
||||
<input
|
||||
id="domain"
|
||||
type="text"
|
||||
name="domain"
|
||||
placeholder="example.org"
|
||||
className="block textbox non-passphrase-input !pl-[60px]"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.domain || ''}
|
||||
/>
|
||||
</div>
|
||||
{formik.touched.domain && formik.errors.domain ? (
|
||||
<div className="ml-1 text-red-500">{formik.errors.domain}</div>
|
||||
) : null}
|
||||
|
||||
@@ -119,6 +119,6 @@ This documentation supports Markdown formatting! You can use:
|
||||
- **Bold** and *italic* text
|
||||
- \`code blocks\` and syntax highlighting
|
||||
- Tables and lists
|
||||
- [Links](https://example.com)
|
||||
- [Links](https://usebruno.com)
|
||||
- And more!
|
||||
`;
|
||||
|
||||
@@ -72,7 +72,7 @@ const Info = ({ collection }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showShareCollectionModal && <ShareCollection collection={collection} onClose={handleToggleShowShareCollectionModal(false)} />}
|
||||
{showShareCollectionModal && <ShareCollection collectionUid={collection.uid} onClose={handleToggleShowShareCollectionModal(false)} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,6 +53,7 @@ const Script = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
@@ -66,6 +67,7 @@ const Script = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const Tests = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -49,7 +49,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
const requestVars = get(collection, 'root.request.vars.req', []);
|
||||
const responseVars = get(collection, 'root.request.vars.res', []);
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
|
||||
const auth = get(collection, 'root.request.auth', {}).mode;
|
||||
const authMode = get(collection, 'root.request.auth', {}).mode || 'none';
|
||||
|
||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||
const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []);
|
||||
@@ -155,7 +155,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||
Auth
|
||||
{auth !== 'none' && <ContentIndicator />}
|
||||
{authMode !== 'none' && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||
Script
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCheck } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -13,11 +13,18 @@ import { variableNameRegex } from 'utils/common/regex';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { getGlobalEnvironmentVariables } from 'utils/collections';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const addButtonRef = useRef(null);
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
|
||||
let _collection = cloneDeep(collection);
|
||||
|
||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||
_collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
@@ -160,7 +167,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
<SingleLineEditor
|
||||
theme={storedTheme}
|
||||
collection={collection}
|
||||
collection={_collection}
|
||||
name={`${index}.value`}
|
||||
value={variable.value}
|
||||
isSecret={variable.secret}
|
||||
|
||||
@@ -28,7 +28,10 @@ const ImportEnvironment = ({ collection, onClose }) => {
|
||||
.then(() => {
|
||||
toast.success('Environment imported successfully');
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||
.catch((error) => {
|
||||
toast.error('An error occurred while importing the environment');
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -11,6 +11,12 @@ const Wrapper = styled.div`
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
.inherit-mode-text {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
.auth-mode-label {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
export default Wrapper;
|
||||
226
packages/bruno-app/src/components/FolderSettings/Auth/index.js
Normal file
226
packages/bruno-app/src/components/FolderSettings/Auth/index.js
Normal file
@@ -0,0 +1,226 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import OAuth2AuthorizationCode from 'components/RequestPane/Auth/OAuth2/AuthorizationCode/index';
|
||||
import { updateFolderAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/PasswordCredentials/index';
|
||||
import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index';
|
||||
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
|
||||
import AuthMode from '../AuthMode';
|
||||
import BasicAuth from 'components/RequestPane/Auth/BasicAuth';
|
||||
import BearerAuth from 'components/RequestPane/Auth/BearerAuth';
|
||||
import DigestAuth from 'components/RequestPane/Auth/DigestAuth';
|
||||
import NTLMAuth from 'components/RequestPane/Auth/NTLMAuth';
|
||||
import WsseAuth from 'components/RequestPane/Auth/WsseAuth';
|
||||
import ApiKeyAuth from 'components/RequestPane/Auth/ApiKeyAuth';
|
||||
import AwsV4Auth from 'components/RequestPane/Auth/AwsV4Auth';
|
||||
import { findItemInCollection, findParentItemInCollection, humanizeRequestAuthMode } from 'utils/collections/index';
|
||||
|
||||
const GrantTypeComponentMap = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const save = () => {
|
||||
dispatch(saveFolderRoot(collection.uid, folder.uid));
|
||||
};
|
||||
|
||||
let request = get(folder, 'root.request', {});
|
||||
const grantType = get(request, 'auth.oauth2.grantType', 'authorization_code');
|
||||
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
return <OAuth2PasswordCredentials save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
case 'authorization_code':
|
||||
return <OAuth2AuthorizationCode save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials save={save} item={folder} request={request} updateAuth={updateFolderAuth} collection={collection} folder={folder} />;
|
||||
default:
|
||||
return <div>TBD</div>;
|
||||
}
|
||||
};
|
||||
|
||||
const Auth = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
let request = get(folder, 'root.request', {});
|
||||
const authMode = get(folder, 'root.request.auth.mode');
|
||||
|
||||
const getTreePathFromCollectionToFolder = (collection, _folder) => {
|
||||
let path = [];
|
||||
let item = findItemInCollection(collection, _folder?.uid);
|
||||
while (item) {
|
||||
path.unshift(item);
|
||||
item = findParentItemInCollection(collection, item?.uid);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
const getEffectiveAuthSource = () => {
|
||||
if (authMode !== 'inherit') return null;
|
||||
|
||||
const collectionAuth = get(collection, 'root.request.auth');
|
||||
let effectiveSource = {
|
||||
type: 'collection',
|
||||
name: 'Collection',
|
||||
auth: collectionAuth
|
||||
};
|
||||
|
||||
// Get path from collection to current folder
|
||||
const folderTreePath = getTreePathFromCollectionToFolder(collection, folder);
|
||||
|
||||
// Check parent folders to find closest auth configuration
|
||||
// Skip the last item which is the current folder
|
||||
for (let i = 0; i < folderTreePath.length - 1; i++) {
|
||||
const parentFolder = folderTreePath[i];
|
||||
if (parentFolder.type === 'folder') {
|
||||
const folderAuth = get(parentFolder, 'root.request.auth');
|
||||
if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') {
|
||||
effectiveSource = {
|
||||
type: 'folder',
|
||||
name: parentFolder.name,
|
||||
auth: folderAuth
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return effectiveSource;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(saveFolderRoot(collection.uid, folder.uid));
|
||||
};
|
||||
|
||||
const getAuthView = () => {
|
||||
switch (authMode) {
|
||||
case 'basic': {
|
||||
return (
|
||||
<BasicAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'bearer': {
|
||||
return (
|
||||
<BearerAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'digest': {
|
||||
return (
|
||||
<DigestAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'ntlm': {
|
||||
return (
|
||||
<NTLMAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'wsse': {
|
||||
return (
|
||||
<WsseAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'apikey': {
|
||||
return (
|
||||
<ApiKeyAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'awsv4': {
|
||||
return (
|
||||
<AwsV4Auth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'oauth2': {
|
||||
return (
|
||||
<>
|
||||
<GrantTypeSelector
|
||||
request={request}
|
||||
updateAuth={updateFolderAuth}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
<GrantTypeComponentMap collection={collection} folder={folder} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'inherit': {
|
||||
const source = getEffectiveAuthSource();
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
<div>Auth inherited from {source.name}: </div>
|
||||
<div className="inherit-mode-text">{humanizeRequestAuthMode(source.auth?.mode)}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'none': {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
Configures authentication for the entire folder. This applies to all requests using the{' '}
|
||||
<span className="font-medium">Inherit</span> option in the <span className="font-medium">Auth</span> tab.
|
||||
</div>
|
||||
<div className="flex flex-grow justify-start items-center mb-4">
|
||||
<AuthMode collection={collection} folder={folder} />
|
||||
</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;
|
||||
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.auth-mode-selector {
|
||||
border: 1px solid ${({ theme }) => theme.colors.border};
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.auth-mode-label {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,134 @@
|
||||
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 { updateFolderAuthMode } from 'providers/ReduxStore/slices/collections';
|
||||
import { humanizeRequestAuthMode } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const AuthMode = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
const authMode = get(folder, '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(
|
||||
updateFolderAuthMode({
|
||||
mode: value,
|
||||
collectionUid: collection.uid,
|
||||
folderUid: folder.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer">
|
||||
<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('digest');
|
||||
}}
|
||||
>
|
||||
Digest Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('ntlm');
|
||||
}}
|
||||
>
|
||||
NTLM Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('oauth2');
|
||||
}}
|
||||
>
|
||||
OAuth 2.0
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('wsse');
|
||||
}}
|
||||
>
|
||||
WSSE Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('apikey');
|
||||
}}
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('inherit');
|
||||
}}
|
||||
>
|
||||
Inherit
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('none');
|
||||
}}
|
||||
>
|
||||
No Auth
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthMode;
|
||||
@@ -9,6 +9,7 @@ import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
|
||||
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||
|
||||
const Headers = ({ collection, folder }) => {
|
||||
@@ -117,6 +118,7 @@ const Headers = ({ collection, folder }) => {
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
autocomplete={MimeTypes}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -55,6 +55,7 @@ const Script = ({ collection, folder }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
@@ -68,6 +69,7 @@ const Script = ({ collection, folder }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ const Tests = ({ collection, folder }) => {
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -8,7 +8,9 @@ import Tests from './Tests';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars';
|
||||
import Documentation from './Documentation';
|
||||
import Auth from './Auth';
|
||||
import DotIcon from 'components/Icons/Dot';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const ContentIndicator = () => {
|
||||
return (
|
||||
@@ -26,7 +28,7 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
tab = folderLevelSettingsSelectedTab[folder?.uid];
|
||||
}
|
||||
|
||||
const folderRoot = collection?.items.find((item) => item.uid === folder?.uid)?.root;
|
||||
const folderRoot = folder?.root;
|
||||
const hasScripts = folderRoot?.request?.script?.res || folderRoot?.request?.script?.req;
|
||||
const hasTests = folderRoot?.request?.tests;
|
||||
|
||||
@@ -37,6 +39,9 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
const responseVars = folderRoot?.request?.vars?.res || [];
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
|
||||
|
||||
const auth = get(folderRoot, 'request.auth.mode');
|
||||
const hasAuth = auth && auth !== 'none';
|
||||
|
||||
const setTab = (tab) => {
|
||||
dispatch(
|
||||
updatedFolderSettingsSelectedTab({
|
||||
@@ -61,6 +66,9 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
case 'vars': {
|
||||
return <Vars collection={collection} folder={folder} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth collection={collection} folder={folder} />;
|
||||
}
|
||||
case 'docs': {
|
||||
return <Documentation collection={collection} folder={folder} />;
|
||||
}
|
||||
@@ -93,6 +101,10 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
Vars
|
||||
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||
Auth
|
||||
{hasAuth && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
|
||||
Docs
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,10 @@ const ImportEnvironment = ({ onClose }) => {
|
||||
.then(() => {
|
||||
toast.success('Global Environment imported successfully');
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||
.catch((error) => {
|
||||
toast.error('An error occurred while importing the environment');
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ const DotIcon = ({ width }) => {
|
||||
className='inline-block'
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor" />
|
||||
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" strokeWidth="0" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,14 +2,10 @@ import React, { Component } from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import { setupAutoComplete } from 'utils/codemirror/autocomplete';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
}
|
||||
const CodeMirror = require('codemirror');
|
||||
|
||||
class MultiLineEditor extends Component {
|
||||
constructor(props) {
|
||||
@@ -78,14 +74,21 @@ class MultiLineEditor extends Component {
|
||||
'Shift-Tab': false
|
||||
}
|
||||
});
|
||||
if (this.props.autocomplete) {
|
||||
this.editor.on('keyup', (cm, event) => {
|
||||
if (!cm.state.completionActive /*Enables keyboard navigation in autocomplete list*/ && event.keyCode != 13) {
|
||||
/*Enter - do not open autocomplete list just after item has been selected in it*/
|
||||
CodeMirror.commands.autocomplete(cm, CodeMirror.hint.anyword, { autocomplete: this.props.autocomplete });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup AutoComplete Helper
|
||||
const autoCompleteOptions = {
|
||||
showHintsFor: ['variables'],
|
||||
anywordAutocompleteHints: this.props.autocomplete
|
||||
};
|
||||
|
||||
const getVariables = () => getAllVariables(this.props.collection, this.props.item);
|
||||
|
||||
this.brunoAutoCompleteCleanup = setupAutoComplete(
|
||||
this.editor,
|
||||
getVariables,
|
||||
autoCompleteOptions
|
||||
);
|
||||
|
||||
this.editor.setValue(String(this.props.value) || '');
|
||||
this.editor.on('change', this._onEdit);
|
||||
this.addOverlay(variables);
|
||||
@@ -125,12 +128,15 @@ class MultiLineEditor extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.brunoAutoCompleteCleanup) {
|
||||
this.brunoAutoCompleteCleanup();
|
||||
}
|
||||
this.editor.getWrapperElement().remove();
|
||||
}
|
||||
|
||||
addOverlay = (variables) => {
|
||||
this.variables = variables;
|
||||
defineCodeMirrorBrunoVariablesMode(variables, 'text/plain');
|
||||
defineCodeMirrorBrunoVariablesMode(variables, 'text/plain', false, true);
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import get from 'lodash/get';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { savePreferences } from 'providers/ReduxStore/slices/app';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const Font = ({ close }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -31,7 +32,10 @@ const Font = ({ close }) => {
|
||||
}
|
||||
})
|
||||
).then(() => {
|
||||
toast.success('Preferences saved successfully')
|
||||
close();
|
||||
}).catch(() => {
|
||||
toast.error('Failed to save preferences')
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -80,9 +80,9 @@ const General = ({ close }) => {
|
||||
storeCookies: newPreferences.storeCookies,
|
||||
sendCookies: newPreferences.sendCookies
|
||||
}
|
||||
})
|
||||
)
|
||||
}))
|
||||
.then(() => {
|
||||
toast.success('Preferences saved successfully')
|
||||
close();
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update preferences'));
|
||||
@@ -125,7 +125,7 @@ const General = ({ close }) => {
|
||||
className="mousetrap mr-0"
|
||||
/>
|
||||
<label className="block ml-2 select-none" htmlFor="customCaCertificateEnabled">
|
||||
Use custom CA Certificate
|
||||
Use Custom CA Certificate
|
||||
</label>
|
||||
</div>
|
||||
{formik.values.customCaCertificate.filePath ? (
|
||||
@@ -183,7 +183,7 @@ const General = ({ close }) => {
|
||||
className={`block ml-2 select-none ${formik.values.customCaCertificate.enabled ? '' : 'opacity-25'}`}
|
||||
htmlFor="keepDefaultCaCertificatesEnabled"
|
||||
>
|
||||
Keep default CA Certificates
|
||||
Keep Default CA Certificates
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
|
||||
@@ -84,7 +84,10 @@ const ProxySettings = ({ close }) => {
|
||||
proxy: validatedProxy
|
||||
})
|
||||
).then(() => {
|
||||
toast.success('Preferences saved successfully')
|
||||
close();
|
||||
}).catch(() => {
|
||||
toast.error('Failed to save preferences')
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -5,21 +5,23 @@ import { IconCaretDown } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
|
||||
|
||||
const ApiKeyAuth = ({ item, collection }) => {
|
||||
const ApiKeyAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const apikeyAuth = item.draft ? get(item, 'draft.request.auth.apikey', {}) : get(item, 'request.auth.apikey', {});
|
||||
const apikeyAuth = get(request, 'auth.apikey', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
@@ -90,7 +92,7 @@ const ApiKeyAuth = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
handleAuthChange('placement', 'header');
|
||||
}}
|
||||
>
|
||||
@@ -99,11 +101,11 @@ const ApiKeyAuth = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
handleAuthChange('placement', 'queryparams');
|
||||
}}
|
||||
>
|
||||
Query Params
|
||||
Query Param
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -8,14 +8,17 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { update } from 'lodash';
|
||||
|
||||
const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
const AwsV4Auth = ({ item, collection, updateAuth, request, save }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {});
|
||||
const awsv4Auth = get(request, 'auth.awsv4', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const handleAccessKeyIdChange = (accessKeyId) => {
|
||||
dispatch(
|
||||
@@ -25,11 +28,11 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -42,12 +45,12 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -60,12 +63,12 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -78,12 +81,12 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -96,12 +99,12 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: region,
|
||||
profileName: awsv4Auth.profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: region || '',
|
||||
profileName: awsv4Auth.profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -114,12 +117,12 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
accessKeyId: awsv4Auth.accessKeyId,
|
||||
secretAccessKey: awsv4Auth.secretAccessKey,
|
||||
sessionToken: awsv4Auth.sessionToken,
|
||||
service: awsv4Auth.service,
|
||||
region: awsv4Auth.region,
|
||||
profileName: profileName
|
||||
accessKeyId: awsv4Auth.accessKeyId || '',
|
||||
secretAccessKey: awsv4Auth.secretAccessKey || '',
|
||||
sessionToken: awsv4Auth.sessionToken || '',
|
||||
service: awsv4Auth.service || '',
|
||||
region: awsv4Auth.region || '',
|
||||
profileName: profileName || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -7,14 +7,17 @@ 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 BasicAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const basicAuth = item.draft ? get(item, 'draft.request.auth.basic', {}) : get(item, 'request.auth.basic', {});
|
||||
const basicAuth = get(request, 'auth.basic', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
@@ -23,8 +26,8 @@ const BasicAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: basicAuth.password
|
||||
username: username || '',
|
||||
password: basicAuth.password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -37,8 +40,8 @@ const BasicAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: basicAuth.username,
|
||||
password: password
|
||||
username: basicAuth.username || '',
|
||||
password: password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -7,16 +7,18 @@ 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 BearerAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const bearerToken = item.draft
|
||||
? get(item, 'draft.request.auth.bearer.token', '')
|
||||
: get(item, 'request.auth.bearer.token', '');
|
||||
// Use the request prop directly like OAuth2ClientCredentials does
|
||||
const bearerToken = get(request, 'auth.bearer.token', '');
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const handleTokenChange = (token) => {
|
||||
dispatch(
|
||||
|
||||
@@ -3,18 +3,20 @@ 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 DigestAuth = ({ item, collection }) => {
|
||||
const DigestAuth = ({ item, collection, updateAuth, request, save }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const digestAuth = item.draft ? get(item, 'draft.request.auth.digest', {}) : get(item, 'request.auth.digest', {});
|
||||
const digestAuth = get(request, 'auth.digest', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
@@ -23,8 +25,8 @@ const DigestAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: digestAuth.password
|
||||
username: username || '',
|
||||
password: digestAuth.password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -37,8 +39,8 @@ const DigestAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: digestAuth.username,
|
||||
password: password
|
||||
username: digestAuth.username || '',
|
||||
password: password || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -7,14 +7,17 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const NTLMAuth = ({ item, collection }) => {
|
||||
const NTLMAuth = ({ item, collection, request, save, updateAuth }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const ntlmAuth = item.draft ? get(item, 'draft.request.auth.ntlm', {}) : get(item, 'request.auth.ntlm', {});
|
||||
const ntlmAuth = get(request, 'auth.ntlm', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleSave = () => {
|
||||
save();
|
||||
};
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
@@ -23,10 +26,9 @@ const NTLMAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: ntlmAuth.password,
|
||||
domain: ntlmAuth.domain
|
||||
|
||||
username: username || '',
|
||||
password: ntlmAuth.password || '',
|
||||
domain: ntlmAuth.domain || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -39,9 +41,9 @@ const NTLMAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: password,
|
||||
domain: ntlmAuth.domain
|
||||
username: ntlmAuth.username || '',
|
||||
password: password || '',
|
||||
domain: ntlmAuth.domain || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -54,9 +56,9 @@ const NTLMAuth = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: ntlmAuth.password,
|
||||
domain: domain
|
||||
username: ntlmAuth.username || '',
|
||||
password: ntlmAuth.password || '',
|
||||
domain: domain || ''
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -11,6 +11,47 @@ const Wrapper = styled.div`
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
|
||||
.token-placement-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
min-width: 100px;
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.token-placement-label {
|
||||
width: fit-content;
|
||||
// color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -1,28 +1,63 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, forwardRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { IconCaretDown, IconSettings, IconKey, IconHelp, IconAdjustmentsHorizontal } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import { clearOauth2Cache } from 'utils/network/index';
|
||||
import toast from 'react-hot-toast';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
|
||||
const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAuth, collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
const oAuth = get(request, 'auth.oauth2', {});
|
||||
const {
|
||||
callbackUrl,
|
||||
authorizationUrl,
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsPlacement,
|
||||
state,
|
||||
pkce,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken
|
||||
} = oAuth;
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
};
|
||||
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
|
||||
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
|
||||
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const TokenPlacementIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
|
||||
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const { callbackUrl, authorizationUrl, accessTokenUrl, clientId, clientSecret, scope, state, pkce } = oAuth;
|
||||
const CredentialsPlacementIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
|
||||
{credentialsPlacement == 'body' ? 'Request Body' : 'Basic Auth Header'}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleSave = () => { save(); };
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
@@ -40,7 +75,15 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
state,
|
||||
scope,
|
||||
pkce,
|
||||
[key]: value
|
||||
credentialsPlacement,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken,
|
||||
[key]: value,
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -61,30 +104,35 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
clientSecret,
|
||||
state,
|
||||
scope,
|
||||
credentialsPlacement,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
autoFetchToken,
|
||||
pkce: !Boolean(oAuth?.['pkce'])
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearCache = (e) => {
|
||||
clearOauth2Cache(collection?.uid)
|
||||
.then(() => {
|
||||
toast.success('cleared cache successfully');
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
<Oauth2TokenViewer handleRun={handleRun} collection={collection} item={item} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
<div className="flex items-center gap-2.5 mt-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconSettings size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<div className="flex items-center gap-4 w-full" key={`input-${key}`}>
|
||||
<label className="block min-w-[140px]">{label}</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
@@ -99,8 +147,33 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex items-center gap-4 w-full" key={`input-credentials-placement`}>
|
||||
<label className="block min-w-[140px]">Add Credentials to</label>
|
||||
<div className="inline-flex items-center cursor-pointer token-placement-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<CredentialsPlacementIcon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('credentialsPlacement', 'body');
|
||||
}}
|
||||
>
|
||||
Request Body
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('credentialsPlacement', 'basic_auth_header');
|
||||
}}
|
||||
>
|
||||
Basic Auth Header
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row w-full gap-4" key="pkce">
|
||||
<label className="block font-medium">Use PKCE</label>
|
||||
<label className="block">Use PKCE</label>
|
||||
<input
|
||||
className="cursor-pointer"
|
||||
type="checkbox"
|
||||
@@ -108,16 +181,154 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
onChange={handlePKCEToggle}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4">
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Clear Cache
|
||||
</button>
|
||||
<div className="flex items-center gap-2.5 mt-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconKey size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Token
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-name`}>
|
||||
<label className="block min-w-[140px]">Token ID</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['credentialsId'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('credentialsId', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-placement`}>
|
||||
<label className="block min-w-[140px]">Add token to</label>
|
||||
<div className="inline-flex items-center cursor-pointer token-placement-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<TokenPlacementIcon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('tokenPlacement', 'header');
|
||||
}}
|
||||
>
|
||||
Header
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('tokenPlacement', 'url');
|
||||
}}
|
||||
>
|
||||
URL
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
tokenPlacement === 'header' ?
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-prefix`}>
|
||||
<label className="block min-w-[140px]">Header Prefix</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['tokenHeaderPrefix'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenHeaderPrefix', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-query-param-key`}>
|
||||
<label className="block font-medium min-w-[140px]">Query Param Key</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['tokenQueryKey'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenQueryKey', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="flex items-center gap-2.5 mt-4 mb-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconAdjustmentsHorizontal size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Advanced Settings
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 w-full mb-4">
|
||||
<label className="block min-w-[140px]">Refresh Token URL</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={get(request, 'auth.oauth2.refreshTokenUrl', '')}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange("refreshTokenUrl", val)}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 mt-4">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconSettings size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">Settings</span>
|
||||
</div>
|
||||
|
||||
{/* Automatically Fetch Token */}
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(autoFetchToken)}
|
||||
onChange={(e) => handleChange('autoFetchToken', e.target.checked)}
|
||||
className="cursor-pointer ml-1"
|
||||
/>
|
||||
<label className="block min-w-[140px]">Automatically fetch token if not found</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group cursor-pointer">
|
||||
<IconHelp size={16} className="text-gray-500" />
|
||||
<span className="group-hover:opacity-100 pointer-events-none opacity-0 max-w-60 absolute left-0 bottom-full mb-1 w-max p-2 bg-gray-700 text-white text-xs rounded-md transition-opacity duration-200">
|
||||
Automatically fetch a new token when you try to access a resource and don't have one.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Auto Refresh Token (With Refresh URL) */}
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(autoRefreshToken)}
|
||||
onChange={(e) => handleChange('autoRefreshToken', e.target.checked)}
|
||||
className={`cursor-pointer ml-1 ${isAutoRefreshDisabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
disabled={isAutoRefreshDisabled}
|
||||
/>
|
||||
<label className={`block min-w-[140px] ${isAutoRefreshDisabled ? 'text-gray-500' : ''}`}>Auto refresh token (with refresh URL)</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group cursor-pointer">
|
||||
<IconHelp size={16} className="text-gray-500" />
|
||||
<span className="group-hover:opacity-100 pointer-events-none opacity-0 max-w-60 absolute left-0 bottom-full mb-1 w-max p-2 bg-gray-700 text-white text-xs rounded-md transition-opacity duration-200">
|
||||
Automatically refresh your token using the refresh URL when it expires.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2AuthorizationCode;
|
||||
export default OAuth2AuthorizationCode;
|
||||
@@ -11,6 +11,47 @@ const Wrapper = styled.div`
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
|
||||
.token-placement-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
min-width: 100px;
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
min-width: 100px;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.token-placement-label {
|
||||
width: fit-content;
|
||||
// color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -1,26 +1,61 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, forwardRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { IconCaretDown, IconSettings, IconKey, IconAdjustmentsHorizontal, IconHelp } from '@tabler/icons';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
|
||||
import Oauth2ActionButtons from '../Oauth2ActionButtons/index';
|
||||
|
||||
const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
const oAuth = get(request, 'auth.oauth2', {});
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
};
|
||||
const {
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsPlacement,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken
|
||||
} = oAuth;
|
||||
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
|
||||
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
|
||||
|
||||
const { accessTokenUrl, clientId, clientSecret, scope } = oAuth;
|
||||
|
||||
const handleSave = () => { save(); };
|
||||
|
||||
const TokenPlacementIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
|
||||
{tokenPlacement == 'url' ? 'URL' : 'Headers'}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const CredentialsPlacementIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end token-placement-label select-none">
|
||||
{credentialsPlacement == 'body' ? 'Request Body' : 'Basic Auth Header'}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
@@ -34,6 +69,14 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsPlacement,
|
||||
credentialsId,
|
||||
tokenPlacement,
|
||||
tokenHeaderPrefix,
|
||||
tokenQueryKey,
|
||||
refreshTokenUrl,
|
||||
autoRefreshToken,
|
||||
autoFetchToken,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
@@ -42,12 +85,21 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
<Oauth2TokenViewer handleRun={handleRun} collection={collection} item={item} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
<div className="flex items-center gap-2.5 mt-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconSettings size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Configuration
|
||||
</span>
|
||||
</div>
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<div className="flex items-center gap-4 w-full" key={`input-${key}`}>
|
||||
<label className="block min-w-[140px]">{label}</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
@@ -62,9 +114,179 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
<div className="flex items-center gap-4 w-full" key={`input-credentials-placement`}>
|
||||
<label className="block min-w-[140px]">Add Credentials to</label>
|
||||
<div className="inline-flex items-center cursor-pointer token-placement-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<CredentialsPlacementIcon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('credentialsPlacement', 'body');
|
||||
}}
|
||||
>
|
||||
Request Body
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('credentialsPlacement', 'basic_auth_header');
|
||||
}}
|
||||
>
|
||||
Basic Auth Header
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5 mt-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconKey size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Token
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-name`}>
|
||||
<label className="block min-w-[140px]">Token ID</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['credentialsId'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('credentialsId', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-placement`}>
|
||||
<label className="block min-w-[140px]">Add token to</label>
|
||||
<div className="inline-flex items-center cursor-pointer token-placement-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<TokenPlacementIcon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('tokenPlacement', 'header');
|
||||
}}
|
||||
>
|
||||
Header
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleChange('tokenPlacement', 'url');
|
||||
}}
|
||||
>
|
||||
URL
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
tokenPlacement === 'header' ?
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-prefix`}>
|
||||
<label className="block min-w-[140px]">Header Prefix</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['tokenHeaderPrefix'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenHeaderPrefix', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className="flex items-center gap-4 w-full" key={`input-token-query-param-key`}>
|
||||
<label className="block font-medium min-w-[140px]">Query Param Key</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={oAuth['tokenQueryKey'] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange('tokenQueryKey', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="flex items-center gap-2.5 mt-4 mb-2">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconAdjustmentsHorizontal size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
Advanced Settings
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 w-full mb-4">
|
||||
<label className="block min-w-[140px]">Refresh Token URL</label>
|
||||
<div className="single-line-editor-wrapper flex-1">
|
||||
<SingleLineEditor
|
||||
value={get(request, 'auth.oauth2.refreshTokenUrl', '')}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange("refreshTokenUrl", val)}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 mt-4">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconSettings size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">Settings</span>
|
||||
</div>
|
||||
|
||||
{/* Automatically Fetch Token */}
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(autoFetchToken)}
|
||||
onChange={(e) => handleChange('autoFetchToken', e.target.checked)}
|
||||
className="cursor-pointer ml-1"
|
||||
/>
|
||||
<label className="block min-w-[140px]">Automatically fetch token if not found</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group cursor-pointer">
|
||||
<IconHelp size={16} className="text-gray-500" />
|
||||
<span className="group-hover:opacity-100 pointer-events-none opacity-0 max-w-60 absolute left-0 bottom-full mb-1 w-max p-2 bg-gray-700 text-white text-xs rounded-md transition-opacity duration-200">
|
||||
Automatically fetch a new token when you try to access a resource and don't have one.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Auto Refresh Token (With Refresh URL) */}
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(autoRefreshToken)}
|
||||
onChange={(e) => handleChange('autoRefreshToken', e.target.checked)}
|
||||
className={`cursor-pointer ml-1 ${isAutoRefreshDisabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
disabled={isAutoRefreshDisabled}
|
||||
/>
|
||||
<label className={`block min-w-[140px] ${isAutoRefreshDisabled ? 'text-gray-500' : ''}`}>Auto refresh token (with refresh URL)</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative group cursor-pointer">
|
||||
<IconHelp size={16} className="text-gray-500" />
|
||||
<span className="group-hover:opacity-100 pointer-events-none opacity-0 max-w-60 absolute left-0 bottom-full mb-1 w-max p-2 bg-gray-700 text-white text-xs rounded-md transition-opacity duration-200">
|
||||
Automatically refresh your token using the refresh URL when it expires.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Oauth2ActionButtons item={item} request={request} collection={collection} url={accessTokenUrl} credentialsId={credentialsId} />
|
||||
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,18 +3,20 @@ import get from 'lodash/get';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { IconCaretDown, IconKey } from '@tabler/icons';
|
||||
import { humanizeGrantType } from 'utils/collections';
|
||||
import { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const GrantTypeSelector = ({ item, collection }) => {
|
||||
const GrantTypeSelector = ({ item = {}, request, updateAuth, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const oAuth = get(request, 'auth.oauth2', {});
|
||||
const [valuesCache, setValuesCache] = useState({
|
||||
...oAuth
|
||||
});
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end grant-type-label select-none">
|
||||
@@ -24,13 +26,19 @@ const GrantTypeSelector = ({ item, collection }) => {
|
||||
});
|
||||
|
||||
const onGrantTypeChange = (grantType) => {
|
||||
let updatedValues = {
|
||||
...valuesCache,
|
||||
...oAuth,
|
||||
grantType
|
||||
};
|
||||
setValuesCache(updatedValues);
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType
|
||||
...updatedValues
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -46,7 +54,18 @@ const GrantTypeSelector = ({ item, collection }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code'
|
||||
grantType: 'authorization_code',
|
||||
accessTokenUrl: '',
|
||||
username: '',
|
||||
password: '',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
scope: '',
|
||||
credentialsPlacement: 'body',
|
||||
credentialsId: 'credentials',
|
||||
tokenPlacement: 'header',
|
||||
tokenHeaderPrefix: 'Bearer',
|
||||
tokenQueryKey: 'access_token',
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -54,7 +73,14 @@ const GrantTypeSelector = ({ item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<label className="block font-medium mb-2">Grant Type</label>
|
||||
<div className="flex items-center gap-2.5 my-4">
|
||||
<div className="flex items-center px-2.5 py-1.5 bg-indigo-50/50 dark:bg-indigo-500/10 rounded-md">
|
||||
<IconKey size={14} className="text-indigo-500 dark:text-indigo-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium">
|
||||
Grant Type
|
||||
</span>
|
||||
</div>
|
||||
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
@@ -89,4 +115,4 @@ const GrantTypeSelector = ({ item, collection }) => {
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default GrantTypeSelector;
|
||||
export default GrantTypeSelector;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user