mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-03 01:18:32 +00:00
Compare commits
348 Commits
v1.33.0
...
fix/workfl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07682ba7b5 | ||
|
|
8810b9e291 | ||
|
|
89a4cd62bc | ||
|
|
4c1765e9f9 | ||
|
|
8351589994 | ||
|
|
4d063b2d0b | ||
|
|
288628003f | ||
|
|
ced6ddfab5 | ||
|
|
5291bbaef7 | ||
|
|
667b15386c | ||
|
|
086e943c04 | ||
|
|
901c4e1ca2 | ||
|
|
14e9227e07 | ||
|
|
d8a2e6f405 | ||
|
|
5c096cff7b | ||
|
|
0913e668e0 | ||
|
|
f0c3125227 | ||
|
|
a5d23599a1 | ||
|
|
28ca0494e0 | ||
|
|
722d9788ca | ||
|
|
038f2d1f0b | ||
|
|
8f604efc7e | ||
|
|
af182a9c00 | ||
|
|
dd23962958 | ||
|
|
324b7cec51 | ||
|
|
8abf8ff9c8 | ||
|
|
ec5a5c9b56 | ||
|
|
4598acd068 | ||
|
|
062ab00a66 | ||
|
|
650cb47a8b | ||
|
|
08139a8f3e | ||
|
|
51e087efba | ||
|
|
ff5683f19f | ||
|
|
d13e4b3b54 | ||
|
|
2d08567d8d | ||
|
|
b51f8109a2 | ||
|
|
8f241a32ae | ||
|
|
b7fda331dc | ||
|
|
10e0fde2a8 | ||
|
|
9f5f975f70 | ||
|
|
b5bd259a1b | ||
|
|
0633d45a10 | ||
|
|
16e27d2ca4 | ||
|
|
956d5a38e9 | ||
|
|
bfc1101371 | ||
|
|
05be59f00c | ||
|
|
fee631d496 | ||
|
|
d03de2b622 | ||
|
|
31b2818821 | ||
|
|
8a71dfc022 | ||
|
|
3e6204e49b | ||
|
|
dab4bb6a1c | ||
|
|
3c8cb702f5 | ||
|
|
2df7fd6588 | ||
|
|
e5d7cd1be9 | ||
|
|
2bce9b3716 | ||
|
|
5bfcc9b6e7 | ||
|
|
472b5452f7 | ||
|
|
5b04e0c189 | ||
|
|
3da12a05db | ||
|
|
a73d2a02cf | ||
|
|
63d3cb380d | ||
|
|
10a5935a12 | ||
|
|
cf2cb0736e | ||
|
|
6abd063749 | ||
|
|
dbf1cad124 | ||
|
|
00c5298b7d | ||
|
|
27ef28ae9b | ||
|
|
abb0a7b0db | ||
|
|
d2d7638a54 | ||
|
|
5500070b49 | ||
|
|
72b8c547b2 | ||
|
|
15b870996d | ||
|
|
3cb15fc001 | ||
|
|
96d6bf1664 | ||
|
|
f5ff40abfa | ||
|
|
c5de2343e9 | ||
|
|
39e8b66135 | ||
|
|
9f9294d161 | ||
|
|
5f63cc4ab4 | ||
|
|
539d22125c | ||
|
|
36343b30b3 | ||
|
|
b5ae2b2b45 | ||
|
|
704977f20f | ||
|
|
c852257bda | ||
|
|
55eac64ca5 | ||
|
|
52672e67a2 | ||
|
|
d32f987bc6 | ||
|
|
5fe9208089 | ||
|
|
14ecc09cde | ||
|
|
949bf539b8 | ||
|
|
c4be6a88e4 | ||
|
|
99302e3a1d | ||
|
|
1d3cbd2335 | ||
|
|
218d6527df | ||
|
|
42ada4a364 | ||
|
|
548f958a0f | ||
|
|
005eb273bf | ||
|
|
343e6dae47 | ||
|
|
7b86febc87 | ||
|
|
ca5fbea7b6 | ||
|
|
f34711c6e0 | ||
|
|
cd722a2bd9 | ||
|
|
75a9959d47 | ||
|
|
754a15dd58 | ||
|
|
2a6f6704c3 | ||
|
|
7d67239b11 | ||
|
|
e0ab274452 | ||
|
|
776afbd28a | ||
|
|
ca8f96fba0 | ||
|
|
5c1ab647fc | ||
|
|
83e63e749e | ||
|
|
9d94ad9b73 | ||
|
|
395fb188fe | ||
|
|
f09fd19ca0 | ||
|
|
f578c188fb | ||
|
|
0d2b449b27 | ||
|
|
e897dc1eb0 | ||
|
|
85b6cae03d | ||
|
|
d215cf740b | ||
|
|
4b277aa874 | ||
|
|
e9378d7895 | ||
|
|
78aa0d07ae | ||
|
|
e4574e3a56 | ||
|
|
795cd196f2 | ||
|
|
7b935bd206 | ||
|
|
43e892f9b0 | ||
|
|
1f2bee1f90 | ||
|
|
767db75730 | ||
|
|
b6b4b7362f | ||
|
|
54d8fbc478 | ||
|
|
9a2d8bfff3 | ||
|
|
907f6a19ad | ||
|
|
b612da4f3c | ||
|
|
625140d1f4 | ||
|
|
284519cd43 | ||
|
|
26daee5d98 | ||
|
|
87988b6879 | ||
|
|
f8711a91d9 | ||
|
|
582e8e5eac | ||
|
|
236bc48d98 | ||
|
|
a63afd6c0b | ||
|
|
fec99f0780 | ||
|
|
bf142af6d9 | ||
|
|
57a85e535c | ||
|
|
f72d643e02 | ||
|
|
aea25842ce | ||
|
|
0831b610cf | ||
|
|
f871bc0fa2 | ||
|
|
db90d31b3f | ||
|
|
e44dcad01a | ||
|
|
7a8d1624d1 | ||
|
|
0ad0af041b | ||
|
|
ad59e3f8d1 | ||
|
|
b9ec0acab4 | ||
|
|
0d126abfbd | ||
|
|
a5096ce413 | ||
|
|
3bf98aab3b | ||
|
|
9b83cd7b84 | ||
|
|
21f9e80706 | ||
|
|
e8bc32b39b | ||
|
|
380047e025 | ||
|
|
434ae6c70f | ||
|
|
22612a7dbe | ||
|
|
06c0b7c78a | ||
|
|
c154dec2b5 | ||
|
|
3abe611752 | ||
|
|
f3cfacdd43 | ||
|
|
af4b2105be | ||
|
|
7ae64605c2 | ||
|
|
83bbbe3fb3 | ||
|
|
73ea5f155d | ||
|
|
366bd99e92 | ||
|
|
6b6fc9a3dc | ||
|
|
6323b54c38 | ||
|
|
57e6af703c | ||
|
|
09120a96e8 | ||
|
|
f1e6d5eefe | ||
|
|
ba41f17439 | ||
|
|
d37e9aaafa | ||
|
|
5a9bda2a0f | ||
|
|
eaa4f4e57b | ||
|
|
8992a457a8 | ||
|
|
b181aba646 | ||
|
|
47179535d5 | ||
|
|
316b632338 | ||
|
|
dc469afeea | ||
|
|
ee715a6dc6 | ||
|
|
19ad0ecef7 | ||
|
|
993424a2b8 | ||
|
|
086c4c063e | ||
|
|
a6ac98b709 | ||
|
|
22ecd0284f | ||
|
|
33e86a9097 | ||
|
|
3efcdf254e | ||
|
|
ea1f385d1c | ||
|
|
4dcaaab52c | ||
|
|
6326dc3d9c | ||
|
|
a55ed9bd50 | ||
|
|
57d86eb118 | ||
|
|
85c6b2d97f | ||
|
|
0c574aeb1e | ||
|
|
3fe0d43bdc | ||
|
|
67ead9739e | ||
|
|
36021b5b38 | ||
|
|
1e45725ba1 | ||
|
|
d4616c78c8 | ||
|
|
5e5656d268 | ||
|
|
52e01935f5 | ||
|
|
99f912312d | ||
|
|
915ebf3387 | ||
|
|
bb18c532da | ||
|
|
4b4bd3bc95 | ||
|
|
482cb05d63 | ||
|
|
32153c4dbf | ||
|
|
7535b3d4ba | ||
|
|
4dd4800ee9 | ||
|
|
e1ebaabcc7 | ||
|
|
f110d898f5 | ||
|
|
5b6172e5ac | ||
|
|
fd22ff8962 | ||
|
|
5f5cc5eb22 | ||
|
|
eb6944a1c9 | ||
|
|
b4ea101350 | ||
|
|
0a8217e4ab | ||
|
|
514da55923 | ||
|
|
bd2cf554b6 | ||
|
|
2f752085f3 | ||
|
|
f2cfcab091 | ||
|
|
4283bb4bb0 | ||
|
|
707cddea90 | ||
|
|
39a44e9b4f | ||
|
|
fb8c54dd7a | ||
|
|
7400d890e4 | ||
|
|
3a29e2e333 | ||
|
|
1fb4298681 | ||
|
|
6d8cc38946 | ||
|
|
9d4246d74b | ||
|
|
1238bf7270 | ||
|
|
e9d459fa5e | ||
|
|
1c4acf7301 | ||
|
|
6385d00807 | ||
|
|
157389424d | ||
|
|
1b30229903 | ||
|
|
72bd1b4cbf | ||
|
|
4a4481a26f | ||
|
|
0bec17facd | ||
|
|
1c86b5f340 | ||
|
|
917205299a | ||
|
|
916f28633e | ||
|
|
6c2451b6f2 | ||
|
|
24563bdaaf | ||
|
|
28d30b1ef7 | ||
|
|
6442e3ceca | ||
|
|
56c3bf0899 | ||
|
|
11a3ea9fbb | ||
|
|
84095a4183 | ||
|
|
d92dd46d4e | ||
|
|
a752921413 | ||
|
|
44debfd9b9 | ||
|
|
278ca8bf29 | ||
|
|
23c22a96bc | ||
|
|
77d3fa7e1e | ||
|
|
412a0ed078 | ||
|
|
1cb0d4e191 | ||
|
|
aff7c405cd | ||
|
|
59108472a2 | ||
|
|
c4492b5d94 | ||
|
|
7fd7eafdcb | ||
|
|
41040bc296 | ||
|
|
ad5b625655 | ||
|
|
cd629451e4 | ||
|
|
dc77ee7c04 | ||
|
|
c322baa9c8 | ||
|
|
b206b70d2e | ||
|
|
9a325caeee | ||
|
|
d0ef70473d | ||
|
|
4894ac2754 | ||
|
|
df206dc4d9 | ||
|
|
45cc97ee20 | ||
|
|
642413e35c | ||
|
|
abb6490232 | ||
|
|
40001949b8 | ||
|
|
bdfe9c16f1 | ||
|
|
d007feb3d1 | ||
|
|
f827b85f47 | ||
|
|
e025ed8436 | ||
|
|
be4fc2d9ad | ||
|
|
5fa7a75284 | ||
|
|
7ddb8c3f4d | ||
|
|
fa22c728ef | ||
|
|
ca2ee673f3 | ||
|
|
9e07c698d8 | ||
|
|
c4148b9e40 | ||
|
|
a580f88f63 | ||
|
|
fe2b45f9ea | ||
|
|
381103663f | ||
|
|
425c90b6eb | ||
|
|
6bebbfe9f3 | ||
|
|
22bc1d4ac5 | ||
|
|
e8530a1022 | ||
|
|
f8f00f1daa | ||
|
|
9aa84a259c | ||
|
|
4ea141fd73 | ||
|
|
2f9d54151e | ||
|
|
796fa0c27c | ||
|
|
a7f05db1d6 | ||
|
|
0c7d513d9e | ||
|
|
d92e806899 | ||
|
|
d70d4a482b | ||
|
|
c82203a059 | ||
|
|
66bb32a683 | ||
|
|
6588dcf2fd | ||
|
|
432d54acca | ||
|
|
0af5f72374 | ||
|
|
f43775e245 | ||
|
|
b82a2c3312 | ||
|
|
ac67c4c0d8 | ||
|
|
e947a8335a | ||
|
|
cc8f3de8be | ||
|
|
ca6c2ebb03 | ||
|
|
bb14ec22f7 | ||
|
|
dddc79c709 | ||
|
|
b900d3070d | ||
|
|
a0fcb6c91f | ||
|
|
a4ad4f6073 | ||
|
|
a2d9249515 | ||
|
|
a880e030eb | ||
|
|
3ef7df57e2 | ||
|
|
40fad99803 | ||
|
|
43cb2b82f3 | ||
|
|
2fc79e0e7f | ||
|
|
1fe7af4fad | ||
|
|
de2053f988 | ||
|
|
1c110f0cb0 | ||
|
|
5fd6773f43 | ||
|
|
9c2c86baf6 | ||
|
|
62babef678 | ||
|
|
a703b84681 | ||
|
|
9ba03a5f02 | ||
|
|
cdf56fcec1 | ||
|
|
f27e79cb01 | ||
|
|
40872f6e9e | ||
|
|
b7f4edac24 | ||
|
|
8e99ed3258 | ||
|
|
c3c91d61c8 | ||
|
|
39f60daca7 | ||
|
|
dce1481185 |
5
.github/workflows/npm-bru-cli.yml
vendored
5
.github/workflows/npm-bru-cli.yml
vendored
@@ -20,7 +20,10 @@ permissions:
|
||||
jobs:
|
||||
test:
|
||||
name: CLI Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -10,6 +10,8 @@ jobs:
|
||||
name: Unit Tests
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
@@ -49,6 +51,10 @@ jobs:
|
||||
cli-test:
|
||||
name: CLI Tests
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
@@ -48,7 +48,7 @@ Bruno is being developed as a desktop app. You need to load the app by running t
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# use nodejs 18 version
|
||||
# use nodejs 20 version
|
||||
nvm use
|
||||
|
||||
# install deps
|
||||
|
||||
20243
package-lock.json
generated
20243
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -30,11 +30,13 @@
|
||||
"ts-jest": "^29.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "node ./scripts/setup.js",
|
||||
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
|
||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
|
||||
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
|
||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||
@@ -51,10 +53,6 @@
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup":"3.29.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"json-bigint": "^1.0.0",
|
||||
"lossless-json": "^4.0.1"
|
||||
"rollup": "3.29.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"presets": ["next/babel"],
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [["styled-components", { "ssr": true }]]
|
||||
}
|
||||
4
packages/bruno-app/.gitignore
vendored
4
packages/bruno-app/.gitignore
vendored
@@ -31,4 +31,6 @@ yarn-error.log*
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
dist/
|
||||
|
||||
.env
|
||||
16
packages/bruno-app/jest.config.js
Normal file
16
packages/bruno-app/jest.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
rootDir: '.',
|
||||
moduleNameMapper: {
|
||||
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||
'^hooks/(.*)$': '<rootDir>/src/hooks/$1',
|
||||
'^themes/(.*)$': '<rootDir>/src/themes/$1',
|
||||
'^api/(.*)$': '<rootDir>/src/api/$1',
|
||||
'^pageComponents/(.*)$': '<rootDir>/src/pageComponents/$1',
|
||||
'^providers/(.*)$': '<rootDir>/src/providers/$1',
|
||||
'^utils/(.*)$': '<rootDir>/src/utils/$1'
|
||||
},
|
||||
clearMocks: true,
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
testEnvironment: 'node'
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
module.exports = {
|
||||
reactStrictMode: false,
|
||||
publicRuntimeConfig: {
|
||||
CI: process.env.CI,
|
||||
PLAYWRIGHT: process.env.PLAYWRIGHT,
|
||||
ENV: process.env.ENV
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
// Fixes npm packages that depend on `fs` module
|
||||
if (!isServer) {
|
||||
config.resolve.fallback.fs = false;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
};
|
||||
@@ -3,74 +3,69 @@
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env ENV=dev next dev -p 3000",
|
||||
"build": "next build && next export",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"dev": "rsbuild dev",
|
||||
"build": "rsbuild build -m production",
|
||||
"preview": "rsbuild preview",
|
||||
"test": "jest",
|
||||
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@fontsource/inter": "^5.0.15",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"@prantlf/jsonlint": "^16.0.0",
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
"axios": "^1.5.1",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "5.65.2",
|
||||
"codemirror-graphql": "1.2.5",
|
||||
"cookie": "^0.6.0",
|
||||
"codemirror-graphql": "2.1.1",
|
||||
"cookie": "0.7.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"file": "^0.2.2",
|
||||
"file-dialog": "^0.0.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.2.9",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"graphiql": "^1.5.9",
|
||||
"graphiql": "3.7.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"httpsnippet": "^3.0.6",
|
||||
"i18next": "^23.14.0",
|
||||
"httpsnippet": "^3.0.9",
|
||||
"i18next": "24.1.2",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.15",
|
||||
"jsesc": "^3.0.2",
|
||||
"jshint": "^2.13.6",
|
||||
"json5": "^2.2.3",
|
||||
"jsonc-parser": "^3.2.1",
|
||||
"jsonlint": "^1.6.3",
|
||||
"jsonpath-plus": "^7.2.0",
|
||||
"jsonpath-plus": "10.2.0",
|
||||
"know-your-http-well": "^0.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-replace-link": "^1.2.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "12.3.3",
|
||||
"nanoid": "3.3.8",
|
||||
"path": "^0.12.7",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"pdfjs-dist": "4.4.168",
|
||||
"platform": "^1.3.6",
|
||||
"posthog-node": "^2.1.0",
|
||||
"posthog-node": "4.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "18.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-inspector": "^6.0.2",
|
||||
"react-pdf": "^7.5.1",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-pdf": "9.1.1",
|
||||
"react-player": "^2.16.0",
|
||||
"react-redux": "^7.2.9",
|
||||
"react-tooltip": "^5.5.2",
|
||||
"sass": "^1.46.0",
|
||||
"strip-json-comments": "^5.0.1",
|
||||
@@ -82,20 +77,21 @@
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/plugin-transform-spread": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^8.2.3",
|
||||
"@rsbuild/core": "^1.1.2",
|
||||
"@rsbuild/plugin-babel": "^1.0.3",
|
||||
"@rsbuild/plugin-node-polyfill": "^1.2.0",
|
||||
"@rsbuild/plugin-react": "^1.0.7",
|
||||
"@rsbuild/plugin-sass": "^1.1.0",
|
||||
"@rsbuild/plugin-styled-components": "1.1.0",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"css-loader": "7.1.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss": "8.4.47",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"webpack": "^5.64.4",
|
||||
|
||||
39
packages/bruno-app/rsbuild.config.mjs
Normal file
39
packages/bruno-app/rsbuild.config.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { pluginBabel } from '@rsbuild/plugin-babel';
|
||||
import { pluginStyledComponents } from '@rsbuild/plugin-styled-components';
|
||||
import { pluginSass } from '@rsbuild/plugin-sass';
|
||||
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
pluginNodePolyfill(),
|
||||
pluginReact(),
|
||||
pluginStyledComponents(),
|
||||
pluginSass(),
|
||||
pluginBabel({
|
||||
include: /\.(?:js|jsx|tsx)$/,
|
||||
babelLoaderOptions(opts) {
|
||||
opts.plugins?.unshift('babel-plugin-react-compiler');
|
||||
}
|
||||
})
|
||||
],
|
||||
source: {
|
||||
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file
|
||||
},
|
||||
html: {
|
||||
title: 'Bruno'
|
||||
},
|
||||
tools: {
|
||||
rspack: {
|
||||
module: {
|
||||
parser: {
|
||||
javascript: {
|
||||
// This loads the JavaScript contents from a library along with the main JavaScript bundle.
|
||||
dynamicImportMode: "eager",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -8,12 +8,17 @@ const StyledWrapper = styled.div`
|
||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||
line-break: anywhere;
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
/* Removes the glow outline around the folded json */
|
||||
.CodeMirror-foldmarker {
|
||||
text-shadow: none;
|
||||
color: ${(props) => props.theme.textLink};
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
@@ -23,6 +28,16 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.CodeMirror-dialog {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
top: unset;
|
||||
left: unset;
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border: 1px solid #d3d6db;
|
||||
outline: none;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#search-results-count {
|
||||
@@ -75,6 +90,18 @@ const StyledWrapper = styled.div`
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.CodeMirror-search-hint {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-property {
|
||||
color: #1f61a0 !important;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-variable {
|
||||
color: #397d13 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
|
||||
import React from 'react';
|
||||
import { isEqual, escapeRegExp } from 'lodash';
|
||||
import { getEnvironmentVariables } from 'utils/collections';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import jsonlint from 'jsonlint';
|
||||
import * as jsonlint from '@prantlf/jsonlint';
|
||||
import { JSHINT } from 'jshint';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const TAB_SIZE = 2;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
@@ -58,13 +58,14 @@ if (!SERVER_RENDERED) {
|
||||
'req.getExecutionMode()',
|
||||
'bru',
|
||||
'bru.cwd()',
|
||||
'bru.getEnvName(key)',
|
||||
'bru.getEnvName()',
|
||||
'bru.getProcessEnv(key)',
|
||||
'bru.hasEnvVar(key)',
|
||||
'bru.getEnvVar(key)',
|
||||
'bru.getFolderVar(key)',
|
||||
'bru.getCollectionVar(key)',
|
||||
'bru.setEnvVar(key,value)',
|
||||
'bru.deleteEnvVar(key)',
|
||||
'bru.hasVar(key)',
|
||||
'bru.getVar(key)',
|
||||
'bru.setVar(key,value)',
|
||||
@@ -73,9 +74,16 @@ if (!SERVER_RENDERED) {
|
||||
'bru.setNextRequest(requestName)',
|
||||
'req.disableParsingResponseJson()',
|
||||
'bru.getRequestVar(key)',
|
||||
'bru.runRequest(requestPathName)',
|
||||
'bru.getAssertionResults()',
|
||||
'bru.getTestResults()',
|
||||
'bru.sleep(ms)',
|
||||
'bru.getGlobalEnvVar(key)',
|
||||
'bru.setGlobalEnvVar(key, value)'
|
||||
'bru.setGlobalEnvVar(key, value)',
|
||||
'bru.runner',
|
||||
'bru.runner.setNextRequest(requestName)',
|
||||
'bru.runner.skipRequest()',
|
||||
'bru.runner.stopExecution()',
|
||||
];
|
||||
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
||||
const cursor = editor.getCursor();
|
||||
@@ -97,7 +105,7 @@ if (!SERVER_RENDERED) {
|
||||
if (curWordBru) {
|
||||
hintWords.forEach((h) => {
|
||||
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
|
||||
result.list.push(curWordBru.includes('.') ? h.split('.')[1] : h);
|
||||
result.list.push(curWordBru.includes('.') ? h.split('.')?.at(-1) : h);
|
||||
}
|
||||
});
|
||||
result.list?.sort();
|
||||
@@ -189,31 +197,19 @@ export default class CodeEditor extends React.Component {
|
||||
'Cmd-Y': 'foldAll',
|
||||
'Ctrl-I': 'unfoldAll',
|
||||
'Cmd-I': 'unfoldAll',
|
||||
'Cmd-/': (cm) => {
|
||||
// comment/uncomment every selected line(s)
|
||||
const selections = cm.listSelections();
|
||||
selections.forEach((range) => {
|
||||
for (let i = range.from().line; i <= range.to().line; i++) {
|
||||
const selectedLine = cm.getLine(i);
|
||||
// if commented line, remove comment
|
||||
if (selectedLine.trim().startsWith('//')) {
|
||||
cm.replaceRange(
|
||||
selectedLine.replace(/^(\s*)\/\/\s?/, '$1'),
|
||||
{ line: i, ch: 0 },
|
||||
{ line: i, ch: selectedLine.length }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// otherwise add comment
|
||||
cm.replaceRange(
|
||||
selectedLine.search(/\S|$/) >= TAB_SIZE
|
||||
? ' '.repeat(TAB_SIZE) + '// ' + selectedLine.trim()
|
||||
: '// ' + selectedLine,
|
||||
{ line: i, ch: 0 },
|
||||
{ line: i, ch: selectedLine.length }
|
||||
);
|
||||
}
|
||||
});
|
||||
'Ctrl-/': () => {
|
||||
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
|
||||
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
|
||||
} else {
|
||||
this.editor.toggleComment();
|
||||
}
|
||||
},
|
||||
'Cmd-/': () => {
|
||||
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
|
||||
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
|
||||
} else {
|
||||
this.editor.toggleComment();
|
||||
}
|
||||
}
|
||||
},
|
||||
foldOptions: {
|
||||
@@ -251,17 +247,20 @@ export default class CodeEditor extends React.Component {
|
||||
return found;
|
||||
}
|
||||
let jsonlint = window.jsonlint.parser || window.jsonlint;
|
||||
jsonlint.parseError = function (str, hash) {
|
||||
let loc = hash.loc;
|
||||
found.push({
|
||||
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
||||
message: str
|
||||
});
|
||||
};
|
||||
try {
|
||||
jsonlint.parse(stripJsonComments(text.replace(/(?<!"[^":{]*){{[^}]*}}(?![^"},]*")/g, '1')));
|
||||
} catch (e) {}
|
||||
} catch (error) {
|
||||
const { message, location } = error;
|
||||
const line = location?.start?.line;
|
||||
const column = location?.start?.column;
|
||||
if (line && column) {
|
||||
found.push({
|
||||
from: CodeMirror.Pos(line - 1, column),
|
||||
to: CodeMirror.Pos(line - 1, column),
|
||||
message
|
||||
});
|
||||
}
|
||||
}
|
||||
return found;
|
||||
});
|
||||
if (editor) {
|
||||
@@ -278,9 +277,9 @@ export default class CodeEditor extends React.Component {
|
||||
while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end;
|
||||
while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start;
|
||||
let curWord = start != end && currentLine.slice(start, end);
|
||||
//Qualify if autocomplete will be shown
|
||||
// Qualify if autocomplete will be shown
|
||||
if (
|
||||
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|\s)\w*/.test(event.key) &&
|
||||
/^(?!Shift|Tab|Enter|Escape|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Meta|Alt|Home|End\s)\w*/.test(event.key) &&
|
||||
curWord.length > 0 &&
|
||||
!/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) &&
|
||||
/(?<!\d)[a-zA-Z\._]$/.test(curWord)
|
||||
@@ -309,7 +308,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
if (this.editor) {
|
||||
let variables = getEnvironmentVariables(this.props.collection);
|
||||
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||
if (!isEqual(variables, this.variables)) {
|
||||
this.addOverlay();
|
||||
}
|
||||
@@ -336,7 +335,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="h-full w-full flex flex-col relative"
|
||||
className="h-full w-full flex flex-col relative graphiql-container"
|
||||
aria-label="Code Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
@@ -349,7 +348,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
addOverlay = () => {
|
||||
const mode = this.props.mode || 'application/ld+json';
|
||||
let variables = getEnvironmentVariables(this.props.collection);
|
||||
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||
this.variables = variables;
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
||||
|
||||
@@ -79,6 +79,15 @@ const AuthMode = ({ collection }) => {
|
||||
>
|
||||
Digest Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('ntlm');
|
||||
}}
|
||||
>
|
||||
NTLM Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const NTLMAuth = ({ collection }) => {
|
||||
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const ntlmAuth = get(collection, 'root.request.auth.ntlm', {});
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: ntlmAuth.password,
|
||||
domain: ntlmAuth.domain
|
||||
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (password) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: password,
|
||||
domain: ntlmAuth.domain
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleDomainChange = (domain) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: ntlmAuth.password,
|
||||
domain: domain
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Username</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.username || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleUsernameChange(val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Password</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.password || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Domain</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.domain || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleDomainChange(val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default NTLMAuth;
|
||||
@@ -1,5 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
const Wrapper = styled.div`
|
||||
max-width: 800px;
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -11,6 +11,8 @@ import ApiKeyAuth from './ApiKeyAuth/';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import OAuth2 from './OAuth2';
|
||||
import NTLMAuth from './NTLMAuth';
|
||||
|
||||
|
||||
const Auth = ({ collection }) => {
|
||||
const authMode = get(collection, 'root.request.auth.mode');
|
||||
@@ -32,6 +34,9 @@ const Auth = ({ collection }) => {
|
||||
case 'digest': {
|
||||
return <DigestAuth collection={collection} />;
|
||||
}
|
||||
case 'ntlm': {
|
||||
return <NTLMAuth collection={collection} />;
|
||||
}
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} />;
|
||||
}
|
||||
|
||||
@@ -68,12 +68,13 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
});
|
||||
|
||||
const getFile = (e) => {
|
||||
if (e.files?.[0]?.path) {
|
||||
const filePath = window?.ipcRenderer?.getFilePath(e?.files?.[0]);
|
||||
if (filePath) {
|
||||
let relativePath;
|
||||
if (isWindowsOS()) {
|
||||
relativePath = slash(path.win32.relative(root, e.files[0].path));
|
||||
relativePath = slash(path.win32.relative(root, filePath));
|
||||
} else {
|
||||
relativePath = path.posix.relative(root, e.files[0].path);
|
||||
relativePath = path.posix.relative(root, filePath);
|
||||
}
|
||||
formik.setFieldValue(e.name, relativePath);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 240px);
|
||||
|
||||
.CodeMirror-scroll {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.editing-mode {
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
|
||||
import Markdown from 'components/MarkDown';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
|
||||
|
||||
const Docs = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -29,19 +30,50 @@ const Docs = ({ collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
const handleDiscardChanges = () => {
|
||||
dispatch(
|
||||
updateCollectionDocs({
|
||||
collectionUid: collection.uid,
|
||||
docs: docs
|
||||
})
|
||||
);
|
||||
toggleViewMode();
|
||||
}
|
||||
|
||||
const onSave = () => {
|
||||
dispatch(saveCollectionRoot(collection.uid));
|
||||
toggleViewMode();
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-1 h-full w-full relative">
|
||||
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
|
||||
{isEditing ? 'Preview' : 'Edit'}
|
||||
<StyledWrapper className="mt-1 h-full w-full relative flex flex-col">
|
||||
<div className='flex flex-row w-full justify-between items-center mb-4'>
|
||||
<div className='text-lg font-medium flex items-center gap-2'>
|
||||
<IconFileText size={20} strokeWidth={1.5} />
|
||||
Documentation
|
||||
</div>
|
||||
<div className='flex flex-row gap-2 items-center justify-center'>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
|
||||
<IconX className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||
</div>
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
|
||||
Save
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
|
||||
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
value={docs || ''}
|
||||
value={docs}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
mode="application/text"
|
||||
@@ -49,10 +81,44 @@ const Docs = ({ collection }) => {
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
) : (
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
<div className='h-full overflow-auto pl-1'>
|
||||
<div className='h-[1px] min-h-[500px]'>
|
||||
{
|
||||
docs?.length > 0 ?
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
:
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={documentationPlaceholder} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Docs;
|
||||
|
||||
|
||||
const documentationPlaceholder = `
|
||||
Welcome to your collection documentation! This space is designed to help you document your API collection effectively.
|
||||
|
||||
## Overview
|
||||
Use this section to provide a high-level overview of your collection. You can describe:
|
||||
- The purpose of these API endpoints
|
||||
- Key features and functionalities
|
||||
- Target audience or users
|
||||
|
||||
## Best Practices
|
||||
- Keep documentation up to date
|
||||
- Include request/response examples
|
||||
- Document error scenarios
|
||||
- Add relevant links and references
|
||||
|
||||
## Markdown Support
|
||||
This documentation supports Markdown formatting! You can use:
|
||||
- **Bold** and *italic* text
|
||||
- \`code blocks\` and syntax highlighting
|
||||
- Tables and lists
|
||||
- [Links](https://example.com)
|
||||
- And more!
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { getTotalRequestCountInCollection } from 'utils/collections/';
|
||||
|
||||
const Info = ({ collection }) => {
|
||||
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col h-full">
|
||||
<div className="text-xs mb-4 text-muted">General information about the collection.</div>
|
||||
<table className="w-full border-collapse">
|
||||
<tbody>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Name :</td>
|
||||
<td className="py-2 px-2">{collection.name}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Location :</td>
|
||||
<td className="py-2 px-2 break-all">{collection.pathname}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Ignored files :</td>
|
||||
<td className="py-2 px-2 break-all">{collection.brunoConfig?.ignore?.map((x) => `'${x}'`).join(', ')}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Environments :</td>
|
||||
<td className="py-2 px-2">{collection.environments?.length || 0}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Requests :</td>
|
||||
<td className="py-2 px-2">{totalRequestsInCollection}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Info;
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { getTotalRequestCountInCollection } from 'utils/collections/';
|
||||
import { IconFolder, IconFileOff, IconWorld, IconApi } from '@tabler/icons';
|
||||
|
||||
const Info = ({ collection }) => {
|
||||
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col h-fit">
|
||||
<div className="rounded-lg py-6">
|
||||
<div className="grid gap-6">
|
||||
{/* Location Row */}
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
<IconFolder className="w-5 h-5 text-blue-500" stroke={1.5} />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<div className="font-semibold text-sm">Location</div>
|
||||
<div className="mt-1 text-sm text-muted break-all">
|
||||
{collection.pathname}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Environments Row */}
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||
<IconWorld className="w-5 h-5 text-green-500" stroke={1.5} />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<div className="font-semibold text-sm">Environments</div>
|
||||
<div className="mt-1 text-sm text-muted">
|
||||
{collection.environments?.length || 0} environment{collection.environments?.length !== 1 ? 's' : ''} configured
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Requests Row */}
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||
<IconApi className="w-5 h-5 text-purple-500" stroke={1.5} />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<div className="font-semibold text-sm">Requests</div>
|
||||
<div className="mt-1 text-sm text-muted">
|
||||
{totalRequestsInCollection} request{totalRequestsInCollection !== 1 ? 's' : ''} in collection
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Info;
|
||||
@@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
&.card {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||
|
||||
.title {
|
||||
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.table {
|
||||
thead {
|
||||
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
|
||||
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { flattenItems } from "utils/collections";
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
|
||||
const RequestsNotLoaded = ({ collection }) => {
|
||||
const flattenedItems = flattenItems(collection.items);
|
||||
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
|
||||
|
||||
if (!itemsFailedLoading?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full card my-2">
|
||||
<div className="flex items-center gap-2 px-3 py-2 title bg-yellow-50 dark:bg-yellow-900/20">
|
||||
<IconAlertTriangle size={16} className="text-yellow-500" />
|
||||
<span className="font-medium">Following requests were not loaded</span>
|
||||
</div>
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-2 px-3 text-left font-medium">
|
||||
Pathname
|
||||
</th>
|
||||
<th className="py-2 px-3 text-left font-medium">
|
||||
Size
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{flattenedItems?.map((item, index) => (
|
||||
item?.partial && !item?.loading ? (
|
||||
<tr key={index}>
|
||||
<td className="py-1.5 px-3">
|
||||
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
|
||||
</td>
|
||||
<td className="py-1.5 px-3">
|
||||
{item?.size?.toFixed?.(2)} MB
|
||||
</td>
|
||||
</tr>
|
||||
) : null
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestsNotLoaded;
|
||||
@@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.partial {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.completed {
|
||||
color: ${(props) => props.theme.colors.text.green};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,27 @@
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
import Docs from "../Docs";
|
||||
import Info from "./Info";
|
||||
import { IconBox } from '@tabler/icons';
|
||||
import RequestsNotLoaded from "./RequestsNotLoaded";
|
||||
|
||||
const Overview = ({ collection }) => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="grid grid-cols-5 gap-4 h-full">
|
||||
<div className="col-span-2">
|
||||
<div className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconBox size={24} stroke={1.5} />
|
||||
{collection?.name}
|
||||
</div>
|
||||
<Info collection={collection} />
|
||||
<RequestsNotLoaded collection={collection} />
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<Docs collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Overview;
|
||||
@@ -1,6 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
.settings-label {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
div.CodeMirror {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
div.tabs {
|
||||
div.tab {
|
||||
padding: 6px 0px;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div``;
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
div.title {
|
||||
color: var(--color-tab-inactive);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,19 @@ import Headers from './Headers';
|
||||
import Auth from './Auth';
|
||||
import Script from './Script';
|
||||
import Test from './Tests';
|
||||
import Docs from './Docs';
|
||||
import Presets from './Presets';
|
||||
import Info from './Info';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars/index';
|
||||
import DotIcon from 'components/Icons/Dot';
|
||||
import Overview from './Overview/index';
|
||||
|
||||
const ContentIndicator = () => {
|
||||
return (
|
||||
<sup className="ml-[.125rem] opacity-80 font-medium">
|
||||
<DotIcon width="10"></DotIcon>
|
||||
</sup>
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionSettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -30,10 +38,23 @@ const CollectionSettings = ({ collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||
const root = collection?.root;
|
||||
const hasScripts = root?.request?.script?.res || root?.request?.script?.req;
|
||||
const hasTests = root?.request?.tests;
|
||||
const hasDocs = root?.docs;
|
||||
|
||||
const headers = get(collection, 'root.request.headers', []);
|
||||
const activeHeadersCount = headers.filter((header) => header.enabled).length;
|
||||
|
||||
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 proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||
const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []);
|
||||
|
||||
|
||||
const onProxySettingsUpdate = (config) => {
|
||||
const brunoConfig = cloneDeep(collection.brunoConfig);
|
||||
brunoConfig.proxy = config;
|
||||
@@ -75,6 +96,9 @@ const CollectionSettings = ({ collection }) => {
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'overview': {
|
||||
return <Overview collection={collection} />;
|
||||
}
|
||||
case 'headers': {
|
||||
return <Headers collection={collection} />;
|
||||
}
|
||||
@@ -106,12 +130,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'docs': {
|
||||
return <Docs collection={collection} />;
|
||||
}
|
||||
case 'info': {
|
||||
return <Info collection={collection} />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -124,35 +142,39 @@ const CollectionSettings = ({ collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('overview')} role="tab" onClick={() => setTab('overview')}>
|
||||
Overview
|
||||
</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
Headers
|
||||
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
|
||||
Vars
|
||||
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||
Auth
|
||||
{auth !== 'none' && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||
Script
|
||||
{hasScripts && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
|
||||
Tests
|
||||
{hasTests && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}>
|
||||
Presets
|
||||
</div>
|
||||
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||
Proxy
|
||||
{Object.keys(proxyConfig).length > 0 && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
|
||||
Client Certificates
|
||||
</div>
|
||||
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
|
||||
Docs
|
||||
</div>
|
||||
<div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}>
|
||||
Info
|
||||
{clientCertConfig.length > 0 && <ContentIndicator />}
|
||||
</div>
|
||||
</div>
|
||||
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
|
||||
|
||||
@@ -3,7 +3,6 @@ import styled from 'styled-components';
|
||||
const StyledWrapper = styled.div`
|
||||
.editing-mode {
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||
{activeEnvironment ? activeEnvironment.name : 'No Environment'}
|
||||
<p className="text-nowrap truncate max-w-32">{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
|
||||
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
@@ -78,7 +78,10 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
||||
<div className="dropdown-item border-top" onClick={() => {
|
||||
handleSettingsIconClick();
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,11 @@ import Modal from 'components/Modal';
|
||||
const CreateEnvironment = ({ collection, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
|
||||
const validateEnvironmentName = (name) => {
|
||||
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
|
||||
};
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
@@ -17,9 +22,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be at least 1 character')
|
||||
.max(50, 'must be 50 characters or less')
|
||||
.required('name is required')
|
||||
.min(1, 'Must be at least 1 character')
|
||||
.max(50, 'Must be 50 characters or less')
|
||||
.required('Name is required')
|
||||
.test('duplicate-name', 'Environment already exists', validateEnvironmentName)
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(addEnvironment(values.name, collection.uid))
|
||||
|
||||
@@ -39,6 +39,11 @@ const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.tooltip-mod {
|
||||
font-size: 11px !important;
|
||||
width: 150px !important;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCheck } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { uuid } from 'utils/common';
|
||||
@@ -11,8 +12,9 @@ import * as Yup from 'yup';
|
||||
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';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
|
||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const addButtonRef = useRef(null);
|
||||
@@ -59,14 +61,15 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
|
||||
const ErrorMessage = ({ name }) => {
|
||||
const meta = formik.getFieldMeta(name);
|
||||
const id = uuid();
|
||||
if (!meta.error || !meta.touched) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<label htmlFor={name} className="text-red-500">
|
||||
{meta.error}
|
||||
</label>
|
||||
<span>
|
||||
<IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
|
||||
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,6 +85,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||
};
|
||||
|
||||
const onActivate = () => {
|
||||
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
onClose();
|
||||
} else {
|
||||
toast.success(`No Environments are active now`);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||
};
|
||||
|
||||
const handleRemoveVar = (id) => {
|
||||
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||
};
|
||||
@@ -124,19 +140,21 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
className="mousetrap"
|
||||
id={`${index}.name`}
|
||||
name={`${index}.name`}
|
||||
value={variable.name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
className="mousetrap"
|
||||
id={`${index}.name`}
|
||||
name={`${index}.name`}
|
||||
value={variable.name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex flex-row flex-nowrap">
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
@@ -179,13 +197,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||
<div className="flex items-center">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-2 flex items-center" onClick={formik.handleSubmit}>
|
||||
<IconDeviceFloppy size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Save
|
||||
</button>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
|
||||
<button type="submit" className="ml-2 px-1 submit btn btn-sm btn-close mt-2 flex items-center" onClick={handleReset}>
|
||||
<IconRefresh size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Reset
|
||||
</button>
|
||||
<button type="submit" className="submit btn btn-sm btn-close mt-2 flex items-center" onClick={onActivate}>
|
||||
<IconCircleCheck size={16} strokeWidth={1.5} className="mr-1" />
|
||||
Activate
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import DeleteEnvironment from '../../DeleteEnvironment';
|
||||
import RenameEnvironment from '../../RenameEnvironment';
|
||||
import EnvironmentVariables from './EnvironmentVariables';
|
||||
|
||||
const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
|
||||
const EnvironmentDetails = ({ environment, collection, setIsModified, onClose }) => {
|
||||
const [openEditModal, setOpenEditModal] = useState(false);
|
||||
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||
const [openCopyModal, setOpenCopyModal] = useState(false);
|
||||
@@ -38,7 +38,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} />
|
||||
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} onClose={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 10px;
|
||||
border-left: solid 2px transparent;
|
||||
text-decoration: none;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
@@ -8,8 +8,10 @@ import ImportEnvironment from '../ImportEnvironment';
|
||||
import ManageSecrets from '../ManageSecrets';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified }) => {
|
||||
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified, onClose }) => {
|
||||
const { environments } = collection;
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openImportModal, setOpenImportModal] = useState(false);
|
||||
@@ -23,6 +25,11 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEnvironment) {
|
||||
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
|
||||
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
|
||||
if (hasSelectedEnvironmentChanged) {
|
||||
setSelectedEnvironment(_selectedEnvironment);
|
||||
}
|
||||
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
||||
return;
|
||||
}
|
||||
@@ -103,13 +110,15 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
||||
{environments &&
|
||||
environments.length &&
|
||||
environments.map((env) => (
|
||||
<div
|
||||
key={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||
>
|
||||
<span className="break-all">{env.name}</span>
|
||||
</div>
|
||||
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
|
||||
<div
|
||||
id={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||
>
|
||||
<span className="break-all">{env.name}</span>
|
||||
</div>
|
||||
</ToolHint>
|
||||
))}
|
||||
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||
+ <span>Create</span>
|
||||
@@ -132,6 +141,7 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
||||
collection={collection}
|
||||
setIsModified={setIsModified}
|
||||
originalEnvironmentVariables={originalEnvironmentVariables}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -72,6 +72,7 @@ const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
collection={collection}
|
||||
isModified={isModified}
|
||||
setIsModified={setIsModified}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -6,10 +6,9 @@ import { IconX } from '@tabler/icons';
|
||||
import { isWindowsOS } from 'utils/common/platform';
|
||||
import slash from 'utils/common/slash';
|
||||
|
||||
const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
value = value || [];
|
||||
const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = false }) => {
|
||||
const dispatch = useDispatch();
|
||||
const filenames = value
|
||||
const filenames = (isSingleFilePicker ? [value] : value || [])
|
||||
.filter((v) => v != null && v != '')
|
||||
.map((v) => {
|
||||
const separator = isWindowsOS() ? '\\' : '/';
|
||||
@@ -20,7 +19,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
const title = filenames.map((v) => `- ${v}`).join('\n');
|
||||
|
||||
const browse = () => {
|
||||
dispatch(browseFiles())
|
||||
dispatch(browseFiles([], [!isSingleFilePicker ? "multiSelections": ""]))
|
||||
.then((filePaths) => {
|
||||
// If file is in the collection's directory, then we use relative path
|
||||
// Otherwise, we use the absolute path
|
||||
@@ -34,7 +33,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
return filePath;
|
||||
});
|
||||
|
||||
onChange(filePaths);
|
||||
onChange(isSingleFilePicker ? filePaths[0] : filePaths);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
@@ -42,14 +41,14 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
onChange([]);
|
||||
onChange(isSingleFilePicker ? '' : []);
|
||||
};
|
||||
|
||||
const renderButtonText = (filenames) => {
|
||||
if (filenames.length == 1) {
|
||||
return filenames[0];
|
||||
}
|
||||
return filenames.length + ' files selected';
|
||||
return filenames.length + ' file(s) selected';
|
||||
};
|
||||
|
||||
return filenames.length > 0 ? (
|
||||
@@ -66,9 +65,9 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-secondary px-1" style={{ width: '100%' }} onClick={browse}>
|
||||
Select Files
|
||||
{isSingleFilePicker ? 'Select File' : 'Select Files'}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePickerEditor;
|
||||
export default FilePickerEditor;
|
||||
@@ -1,12 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
table {
|
||||
td {
|
||||
&:first-child {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
.editing-mode {
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'github-markdown-css/github-markdown.css';
|
||||
import get from 'lodash/get';
|
||||
import { updateFolderDocs } from 'providers/ReduxStore/slices/collections';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Markdown from 'components/MarkDown';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Documentation = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const docs = get(folder, 'root.docs', '');
|
||||
|
||||
const toggleViewMode = () => {
|
||||
setIsEditing((prev) => !prev);
|
||||
};
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
updateFolderDocs({
|
||||
folderUid: folder.uid,
|
||||
collectionUid: collection.uid,
|
||||
docs: value
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
|
||||
|
||||
if (!folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-1 h-full w-full relative flex flex-col">
|
||||
<div className="editing-mode flex justify-between items-center" role="tab" onClick={toggleViewMode}>
|
||||
{isEditing ? 'Preview' : 'Edit'}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="mt-2 flex-1 max-h-[70vh]">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
value={docs || ''}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
mode="application/text"
|
||||
/>
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary my-6" onClick={onSave}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Documentation;
|
||||
@@ -7,6 +7,16 @@ import Script from './Script';
|
||||
import Tests from './Tests';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars';
|
||||
import Documentation from './Documentation';
|
||||
import DotIcon from 'components/Icons/Dot';
|
||||
|
||||
const ContentIndicator = () => {
|
||||
return (
|
||||
<sup className="ml-[.125rem] opacity-80 font-medium">
|
||||
<DotIcon width="10"></DotIcon>
|
||||
</sup>
|
||||
);
|
||||
};
|
||||
|
||||
const FolderSettings = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -16,6 +26,17 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
tab = folderLevelSettingsSelectedTab[folder?.uid];
|
||||
}
|
||||
|
||||
const folderRoot = collection?.items.find((item) => item.uid === folder?.uid)?.root;
|
||||
const hasScripts = folderRoot?.request?.script?.res || folderRoot?.request?.script?.req;
|
||||
const hasTests = folderRoot?.request?.tests;
|
||||
|
||||
const headers = folderRoot?.request?.headers || [];
|
||||
const activeHeadersCount = headers.filter((header) => header.enabled).length;
|
||||
|
||||
const requestVars = folderRoot?.request?.vars?.req || [];
|
||||
const responseVars = folderRoot?.request?.vars?.res || [];
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
|
||||
|
||||
const setTab = (tab) => {
|
||||
dispatch(
|
||||
updatedFolderSettingsSelectedTab({
|
||||
@@ -40,6 +61,9 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
case 'vars': {
|
||||
return <Vars collection={collection} folder={folder} />;
|
||||
}
|
||||
case 'docs': {
|
||||
return <Documentation collection={collection} folder={folder} />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,15 +79,22 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
Headers
|
||||
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||
Script
|
||||
{hasScripts && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('test')} role="tab" onClick={() => setTab('test')}>
|
||||
Test
|
||||
{hasTests && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
|
||||
Vars
|
||||
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
|
||||
</div>
|
||||
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
|
||||
Docs
|
||||
</div>
|
||||
</div>
|
||||
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>
|
||||
|
||||
@@ -7,6 +7,7 @@ import toast from 'react-hot-toast';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
|
||||
const EnvironmentSelector = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -18,12 +19,14 @@ const EnvironmentSelector = () => {
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
|
||||
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||
{
|
||||
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
|
||||
}
|
||||
</div>
|
||||
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
|
||||
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
|
||||
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||
{
|
||||
activeEnvironment ? <div className='text-nowrap truncate max-w-32'>{activeEnvironment?.name}</div> : null
|
||||
}
|
||||
</ToolHint>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,19 @@ import React, { useEffect, useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
|
||||
const CreateEnvironment = ({ onClose }) => {
|
||||
const globalEnvs = useSelector((state) => state?.globalEnvironments?.globalEnvironments);
|
||||
|
||||
const validateEnvironmentName = (name) => {
|
||||
const trimmedName = name?.toLowerCase().trim();
|
||||
return globalEnvs.every((env) => env?.name?.toLowerCase().trim() !== trimmedName);
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
@@ -17,9 +24,10 @@ const CreateEnvironment = ({ onClose }) => {
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be at least 1 character')
|
||||
.max(50, 'must be 50 characters or less')
|
||||
.required('name is required')
|
||||
.min(1, 'Must be at least 1 character')
|
||||
.max(50, 'Must be 50 characters or less')
|
||||
.required('Name is required')
|
||||
.test('duplicate-name', 'Global Environment already exists', validateEnvironmentName)
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(addGlobalEnvironment({ name: values.name }))
|
||||
|
||||
@@ -39,6 +39,11 @@ const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.tooltip-mod {
|
||||
font-size: 11px !important;
|
||||
width: 150px !important;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { IconTrash, IconAlertCircle } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
@@ -11,6 +11,7 @@ import * as Yup from 'yup';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import toast from 'react-hot-toast';
|
||||
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
|
||||
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -62,14 +63,15 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
|
||||
const ErrorMessage = ({ name }) => {
|
||||
const meta = formik.getFieldMeta(name);
|
||||
const id = uuid();
|
||||
if (!meta.error || !meta.touched) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<label htmlFor={name} className="text-red-500">
|
||||
{meta.error}
|
||||
</label>
|
||||
<span>
|
||||
<IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
|
||||
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -127,19 +129,21 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
className="mousetrap"
|
||||
id={`${index}.name`}
|
||||
name={`${index}.name`}
|
||||
value={variable.name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
className="mousetrap"
|
||||
id={`${index}.name`}
|
||||
name={`${index}.name`}
|
||||
value={variable.name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex flex-row flex-nowrap">
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
|
||||
@@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 10px;
|
||||
border-left: solid 2px transparent;
|
||||
text-decoration: none;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
@@ -7,6 +7,8 @@ import StyledWrapper from './StyledWrapper';
|
||||
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
|
||||
import ImportEnvironment from '../ImportEnvironment';
|
||||
import { isEqual } from 'lodash';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
|
||||
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
@@ -20,18 +22,28 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
|
||||
const prevEnvUids = usePrevious(envUids);
|
||||
|
||||
useEffect(() => {
|
||||
if (!environments?.length) {
|
||||
setSelectedEnvironment(null);
|
||||
setOriginalEnvironmentVariables([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedEnvironment) {
|
||||
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
|
||||
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
|
||||
if (hasSelectedEnvironmentChanged) {
|
||||
setSelectedEnvironment(_selectedEnvironment);
|
||||
}
|
||||
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
||||
return;
|
||||
}
|
||||
|
||||
const environment = environments?.find(env => env?.uid === activeEnvironmentUid);
|
||||
if (environment) {
|
||||
setSelectedEnvironment(environment);
|
||||
} else {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
}
|
||||
}, [environments, selectedEnvironment]);
|
||||
const environment = environments?.find(env => env.uid === activeEnvironmentUid) || environments?.[0];
|
||||
|
||||
setSelectedEnvironment(environment);
|
||||
setOriginalEnvironmentVariables(environment?.variables || []);
|
||||
}, [environments, activeEnvironmentUid]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
@@ -101,13 +113,15 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
|
||||
{environments &&
|
||||
environments.length &&
|
||||
environments.map((env) => (
|
||||
<div
|
||||
key={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||
>
|
||||
<span className="break-all">{env.name}</span>
|
||||
</div>
|
||||
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
|
||||
<div
|
||||
id={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle click
|
||||
>
|
||||
<span className="break-all">{env.name}</span>
|
||||
</div>
|
||||
</ToolHint>
|
||||
))}
|
||||
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||
+ <span>Create</span>
|
||||
|
||||
@@ -62,7 +62,7 @@ const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, o
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
|
||||
<Modal size="lg" title="Global Environments" handleCancel={onClose} hideFooter={true}>
|
||||
<EnvironmentList
|
||||
environments={globalEnvironments}
|
||||
activeEnvironmentUid={activeGlobalEnvironmentUid}
|
||||
|
||||
@@ -9,7 +9,6 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
|
||||
h1 {
|
||||
@@ -55,7 +54,7 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
background-color: var(--color-sidebar-collection-item-active-indent-border);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@@ -80,12 +79,6 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledMarkdownBodyWrapper;
|
||||
|
||||
@@ -62,7 +62,7 @@ const Modal = ({
|
||||
confirmText,
|
||||
cancelText,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
handleConfirm = () => {},
|
||||
children,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
@@ -92,7 +92,7 @@ const Modal = ({
|
||||
};
|
||||
|
||||
useFocusTrap(modalRef);
|
||||
|
||||
|
||||
const closeModal = (args) => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||||
@@ -103,7 +103,7 @@ const Modal = ({
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [disableEscapeKey, document]);
|
||||
}, [disableEscapeKey, document, handleConfirm]);
|
||||
|
||||
let classes = 'bruno-modal';
|
||||
if (isClosing) {
|
||||
|
||||
@@ -13,9 +13,14 @@ const StyledWrapper = styled.div`
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: hidden !important;
|
||||
${'' /* padding-bottom: 50px !important; */}
|
||||
pre.CodeMirror-placeholder {
|
||||
color: ${(props) => props.theme.text};
|
||||
padding-left: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: visible !important;
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0px;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
@@ -30,6 +30,7 @@ class MultiLineEditor extends Component {
|
||||
lineWrapping: false,
|
||||
lineNumbers: false,
|
||||
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
||||
placeholder: this.props.placeholder,
|
||||
mode: 'brunovariables',
|
||||
brunoVarInfo: {
|
||||
variables
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.collection-dropdown {
|
||||
color: rgb(110 110 110);
|
||||
|
||||
&:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tippy-box {
|
||||
top: -0.5rem;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, { useState, forwardRef, useRef } from 'react';
|
||||
import Dropdown from '../Dropdown';
|
||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconBox, IconSearch, IconDots } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Navbar = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const menuDropdownTippyRef = useRef();
|
||||
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
||||
const MenuIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="dropdown-icon cursor-pointer">
|
||||
<IconDots size={22} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledWrapper className="px-2 py-2 flex items-center">
|
||||
<div>
|
||||
<span className="ml-2">Collections</span>
|
||||
{/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */}
|
||||
</div>
|
||||
<div className="collection-dropdown flex flex-grow items-center justify-end">
|
||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Collection
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
Import Collection
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
function Portal({ children, wrapperId }) {
|
||||
wrapperId = wrapperId || 'bruno-app-body';
|
||||
|
||||
return createPortal(children, document.getElementById(wrapperId));
|
||||
function Portal({ children }) {
|
||||
return createPortal(children, document.body);
|
||||
}
|
||||
export default Portal;
|
||||
|
||||
@@ -90,7 +90,10 @@ const General = ({ close }) => {
|
||||
};
|
||||
|
||||
const addCaCertificate = (e) => {
|
||||
formik.setFieldValue('customCaCertificate.filePath', e.target.files[0]?.path);
|
||||
const filePath = window?.ipcRenderer?.getFilePath(e?.target?.files?.[0]);
|
||||
if (filePath) {
|
||||
formik.setFieldValue('customCaCertificate.filePath', filePath);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteCaCertificate = () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import React from 'react';
|
||||
* endsWith : ends with
|
||||
* between : between
|
||||
* isEmpty : is empty
|
||||
* isNotEmpty : is not empty
|
||||
* isNull : is null
|
||||
* isUndefined : is undefined
|
||||
* isDefined : is defined
|
||||
@@ -51,6 +52,7 @@ const AssertionOperator = ({ operator, onChange }) => {
|
||||
'endsWith',
|
||||
'between',
|
||||
'isEmpty',
|
||||
'isNotEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useTheme } from 'providers/Theme';
|
||||
* endsWith : ends with
|
||||
* between : between
|
||||
* isEmpty : is empty
|
||||
* isNotEmpty : is not empty
|
||||
* isNull : is null
|
||||
* isUndefined : is undefined
|
||||
* isDefined : is defined
|
||||
@@ -61,6 +62,7 @@ const parseAssertionOperator = (str = '') => {
|
||||
'endsWith',
|
||||
'between',
|
||||
'isEmpty',
|
||||
'isNotEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
@@ -75,6 +77,7 @@ const parseAssertionOperator = (str = '') => {
|
||||
|
||||
const unaryOperators = [
|
||||
'isEmpty',
|
||||
'isNotEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
@@ -87,7 +90,7 @@ const parseAssertionOperator = (str = '') => {
|
||||
'isArray'
|
||||
];
|
||||
|
||||
const [operator, ...rest] = str.trim().split(' ');
|
||||
const [operator, ...rest] = str.split(' ');
|
||||
const value = rest.join(' ');
|
||||
|
||||
if (unaryOperators.includes(operator)) {
|
||||
@@ -113,6 +116,7 @@ const parseAssertionOperator = (str = '') => {
|
||||
const isUnaryOperator = (operator) => {
|
||||
const unaryOperators = [
|
||||
'isEmpty',
|
||||
'isNotEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
@@ -142,19 +146,8 @@ const AssertionRow = ({
|
||||
const { operator, value } = parseAssertionOperator(assertion.value);
|
||||
|
||||
return (
|
||||
<tr key={assertion.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={assertion.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<>
|
||||
|
||||
<td>
|
||||
<AssertionOperator
|
||||
operator={operator}
|
||||
@@ -162,7 +155,7 @@ const AssertionRow = ({
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
value: `${op} ${value}`
|
||||
value: isUnaryOperator(op) ? op : `${op} ${value}`
|
||||
}
|
||||
},
|
||||
assertion,
|
||||
@@ -178,7 +171,7 @@ const AssertionRow = ({
|
||||
theme={storedTheme}
|
||||
readOnly={true}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
onChange={(newValue) => {
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
@@ -188,6 +181,7 @@ const AssertionRow = ({
|
||||
assertion,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
@@ -211,7 +205,7 @@ const AssertionRow = ({
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -15,24 +16,15 @@ const Wrapper = styled.div`
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
select {
|
||||
select {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-assertion {
|
||||
font-size: 0.8125rem;
|
||||
@@ -42,7 +34,8 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
background-color: inherit;
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
|
||||
@@ -6,6 +6,9 @@ import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxS
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import AssertionRow from './AssertionRow';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Table from 'components/Table/index';
|
||||
import ReorderTable from 'components/ReorderTable/index';
|
||||
import { moveAssertion } from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const Assertions = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -57,21 +60,43 @@ const Assertions = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleAssertionDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveAssertion({
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Expr</td>
|
||||
<td>Operator</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Expr', accessor: 'expr', width: '30%' },
|
||||
{ name: 'Operator', accessor: 'operator', width: '120px' },
|
||||
{ name: 'Value', accessor: 'value', width: '30%' },
|
||||
{ name: '', accessor: '', width: '15%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleAssertionDrag}>
|
||||
{assertions && assertions.length
|
||||
? assertions.map((assertion) => {
|
||||
return (
|
||||
return (
|
||||
<tr key={assertion.uid} data-uid={assertion.uid}>
|
||||
<td className='flex relative'>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={assertion.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<AssertionRow
|
||||
key={assertion.uid}
|
||||
assertion={assertion}
|
||||
@@ -82,11 +107,12 @@ const Assertions = ({ item, collection }) => {
|
||||
onSave={onSave}
|
||||
handleRun={handleRun}
|
||||
/>
|
||||
);
|
||||
})
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
||||
+ Add Assertion
|
||||
</button>
|
||||
|
||||
@@ -70,6 +70,15 @@ const AuthMode = ({ item, collection }) => {
|
||||
>
|
||||
Digest Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('ntlm');
|
||||
}}
|
||||
>
|
||||
NTLM Auth
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const NTLMAuth = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const ntlmAuth = item.draft ? get(item, 'draft.request.auth.ntlm', {}) : get(item, 'request.auth.ntlm', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: username,
|
||||
password: ntlmAuth.password,
|
||||
domain: ntlmAuth.domain
|
||||
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (password) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: password,
|
||||
domain: ntlmAuth.domain
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleDomainChange = (domain) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'ntlm',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
username: ntlmAuth.username,
|
||||
password: ntlmAuth.password,
|
||||
domain: domain
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Username</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.username || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleUsernameChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Password</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.password || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Domain</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={ntlmAuth.domain || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleDomainChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default NTLMAuth;
|
||||
@@ -6,6 +6,8 @@ import BearerAuth from './BearerAuth';
|
||||
import BasicAuth from './BasicAuth';
|
||||
import DigestAuth from './DigestAuth';
|
||||
import WsseAuth from './WsseAuth';
|
||||
import NTLMAuth from './NTLMAuth';
|
||||
|
||||
import ApiKeyAuth from './ApiKeyAuth';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
||||
@@ -31,6 +33,9 @@ const Auth = ({ item, collection }) => {
|
||||
case 'digest': {
|
||||
return <DigestAuth collection={collection} item={item} />;
|
||||
}
|
||||
case 'ntlm': {
|
||||
return <NTLMAuth collection={collection} item={item} />;
|
||||
}
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} item={item} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
164
packages/bruno-app/src/components/RequestPane/FileBody/index.js
Normal file
164
packages/bruno-app/src/components/RequestPane/FileBody/index.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { get, cloneDeep, isArray } from 'lodash';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addFile as _addFile, updateFile, deleteFile } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import FilePickerEditor from 'components/FilePickerEditor/index';
|
||||
import SingleLineEditor from 'components/SingleLineEditor/index';
|
||||
|
||||
const FileBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const params = item.draft ? get(item, 'draft.request.body.file') : get(item, 'request.body.file');
|
||||
|
||||
const [enabledFileUid, setEnableFileUid] = useState(params && params.length ? params[0].uid : '');
|
||||
|
||||
const addFile = () => {
|
||||
dispatch(
|
||||
_addFile({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
switch (type) {
|
||||
case 'filePath': {
|
||||
param.filePath = e.target.filePath;
|
||||
param.contentType = "";
|
||||
break;
|
||||
}
|
||||
case 'contentType': {
|
||||
param.contentType = e.target.contentType;
|
||||
break;
|
||||
}
|
||||
case 'selected': {
|
||||
param.selected = e.target.selected;
|
||||
setEnableFileUid(param.uid)
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(
|
||||
updateFile({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveParams = (param) => {
|
||||
dispatch(
|
||||
deleteFile({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">File</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">Content-Type</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">Selected</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<FilePickerEditor
|
||||
isSingleFilePicker={true}
|
||||
value={param.filePath}
|
||||
onChange={(path) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
filePath: path
|
||||
}
|
||||
},
|
||||
param,
|
||||
'filePath'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
className="flex items-center justify-center"
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
placeholder="Auto"
|
||||
value={param.contentType}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
contentType: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'contentType'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">
|
||||
<input
|
||||
key={param.uid}
|
||||
type="radio"
|
||||
name="selected"
|
||||
checked={enabledFileUid === param.uid || param.selected}
|
||||
tabIndex="-1"
|
||||
className="mr-1 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'selected')}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center justify-center">
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<button className="btn-add-param text-link pr-2 pt-3 select-none" onClick={addFile}>
|
||||
+ Add File
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default FileBody;
|
||||
@@ -19,16 +19,8 @@ const Wrapper = styled.div`
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
|
||||
@@ -7,11 +7,14 @@ import { useTheme } from 'providers/Theme';
|
||||
import {
|
||||
addFormUrlEncodedParam,
|
||||
updateFormUrlEncodedParam,
|
||||
deleteFormUrlEncodedParam
|
||||
deleteFormUrlEncodedParam,
|
||||
moveFormUrlEncodedParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ReorderTable from 'components/ReorderTable/index';
|
||||
import Table from 'components/Table/index';
|
||||
|
||||
const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -64,75 +67,84 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveFormUrlEncodedParam({
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Key</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Key', accessor: 'key', width: '40%' },
|
||||
{ name: 'Value', accessor: 'value', width: '46%' },
|
||||
{ name: '', accessor: '', width: '14%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
return (
|
||||
<tr key={param.uid} data-uid={param.uid}>
|
||||
<td className='flex relative'>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<MultiLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
allowNewlines={true}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<MultiLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
allowNewlines={true}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
|
||||
+ Add Param
|
||||
</button>
|
||||
|
||||
@@ -154,7 +154,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
</div>
|
||||
<GraphQLSchemaActions item={item} collection={collection} onSchemaLoad={setSchema} toggleDocs={toggleDocs} />
|
||||
</div>
|
||||
<section className="flex w-full mt-5 flex-1">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||
<section className="flex w-full mt-5 flex-1 relative">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full relative">
|
||||
<>
|
||||
<button
|
||||
className="btn-add-param text-link px-4 py-4 select-none absolute top-0 right-0 z-10"
|
||||
onClick={onPrettify}
|
||||
@@ -68,7 +68,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ import { find, get } from 'lodash';
|
||||
import Documentation from 'components/Documentation/index';
|
||||
|
||||
const ContentIndicator = () => {
|
||||
return <sup className="ml-[.125rem] opacity-80 font-medium">
|
||||
<DotIcon width="10"></DotIcon>
|
||||
</sup>
|
||||
return (
|
||||
<sup className="ml-[.125rem] opacity-80 font-medium">
|
||||
<DotIcon width="10"></DotIcon>
|
||||
</sup>
|
||||
);
|
||||
};
|
||||
|
||||
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
@@ -100,6 +102,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
const docs = getPropertyFromDraftOrRequest('request.docs');
|
||||
const requestVars = getPropertyFromDraftOrRequest('request.vars.req');
|
||||
const responseVars = getPropertyFromDraftOrRequest('request.vars.res');
|
||||
const auth = getPropertyFromDraftOrRequest('request.auth');
|
||||
|
||||
const activeParamsLength = params.filter((param) => param.enabled).length;
|
||||
const activeHeadersLength = headers.filter((header) => header.enabled).length;
|
||||
@@ -125,6 +128,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
|
||||
Auth
|
||||
{auth.mode !== 'none' && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
|
||||
Vars
|
||||
|
||||
@@ -19,15 +19,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
|
||||
@@ -7,12 +7,15 @@ import { useTheme } from 'providers/Theme';
|
||||
import {
|
||||
addMultipartFormParam,
|
||||
updateMultipartFormParam,
|
||||
deleteMultipartFormParam
|
||||
deleteMultipartFormParam,
|
||||
moveMultipartFormParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import FilePickerEditor from 'components/FilePickerEditor';
|
||||
import Table from 'components/Table/index';
|
||||
import ReorderTable from 'components/ReorderTable/index';
|
||||
|
||||
const MultipartFormParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -54,6 +57,10 @@ const MultipartFormParams = ({ item, collection }) => {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'contentType': {
|
||||
param.contentType = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
@@ -78,93 +85,124 @@ const MultipartFormParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveMultipartFormParam({
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Key</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Key', accessor: 'key', width: '29%' },
|
||||
{ name: 'Value', accessor: 'value', width: '29%' },
|
||||
{ name: 'Content-Type', accessor: 'content-type', width: '28%' },
|
||||
{ name: '', accessor: '', width: '14%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
return (
|
||||
<tr key={param.uid} className='w-full' data-uid={param.uid}>
|
||||
<td className="flex relative">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{param.type === 'file' ? (
|
||||
<FilePickerEditor
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{param.type === 'file' ? (
|
||||
<FilePickerEditor
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
) : (
|
||||
<MultiLineEditor
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
allowNewlines={true}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<MultiLineEditor
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
allowNewlines={true}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<MultiLineEditor
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
placeholder="Auto"
|
||||
value={param.contentType}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'contentType'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
<div>
|
||||
<button className="btn-add-param text-link pr-2 pt-3 mt-2 select-none" onClick={addParam}>
|
||||
+ Add Param
|
||||
|
||||
@@ -50,6 +50,18 @@ const StyledWrapper = styled.div`
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.CodeMirror-search-hint {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-property {
|
||||
color: #1f61a0 !important;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-variable {
|
||||
color: #397d13 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IconWand } from '@tabler/icons';
|
||||
import onHasCompletion from './onHasCompletion';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
@@ -209,7 +209,7 @@ export default class QueryEditor extends React.Component {
|
||||
return (
|
||||
<>
|
||||
<StyledWrapper
|
||||
className="h-full w-full flex flex-col relative"
|
||||
className="h-full w-full flex flex-col relative graphiql-container"
|
||||
aria-label="Query Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
|
||||
@@ -103,7 +103,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||
const handleQueryParamDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveQueryParam({
|
||||
collectionUid: collection.uid,
|
||||
@@ -117,7 +117,6 @@ const QueryParams = ({ item, collection }) => {
|
||||
<StyledWrapper className="w-full flex flex-col absolute">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Query</div>
|
||||
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Name', accessor: 'name', width: '31%' },
|
||||
@@ -125,7 +124,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
{ name: '', accessor: '', width: '13%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||
<ReorderTable updateReorderedItem={handleQueryParamDrag}>
|
||||
{queryParams && queryParams.length
|
||||
? queryParams.map((param, index) => (
|
||||
<tr key={param.uid} data-uid={param.uid}>
|
||||
@@ -153,7 +152,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
@@ -188,7 +187,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
</code>
|
||||
</div>
|
||||
`}
|
||||
infotipId="path-param-InfoTip"
|
||||
infotipId="path-param-InfoTip"
|
||||
/>
|
||||
</div>
|
||||
<table>
|
||||
@@ -241,11 +240,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
{!(pathParams && pathParams.length) ?
|
||||
<div className="title pr-2 py-3 mt-2 text-xs">
|
||||
|
||||
</div>
|
||||
: null}
|
||||
{!(pathParams && pathParams.length) ? <div className="title pr-2 py-3 mt-2 text-xs"></div> : null}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
|
||||
const handleGenerateCode = (e) => {
|
||||
e.stopPropagation();
|
||||
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
|
||||
if (item?.request?.url !== '' || (item.draft?.request?.url !== undefined && item.draft?.request?.url !== '')) {
|
||||
setGenerateCodeItemModalOpen(true);
|
||||
} else {
|
||||
toast.error('URL is required');
|
||||
|
||||
@@ -9,7 +9,6 @@ import StyledWrapper from './StyledWrapper';
|
||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { toastError } from 'utils/common/error';
|
||||
import { format, applyEdits } from 'jsonc-parser';
|
||||
import { parse, stringify } from 'lossless-json';
|
||||
import xmlFormat from 'xml-formatter';
|
||||
|
||||
const RequestBodyMode = ({ item, collection }) => {
|
||||
@@ -129,6 +128,15 @@ const RequestBodyMode = ({ item, collection }) => {
|
||||
SPARQL
|
||||
</div>
|
||||
<div className="label-item font-medium">Other</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('file');
|
||||
}}
|
||||
>
|
||||
File / Binary
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useTheme } from 'providers/Theme';
|
||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import FileBody from '../FileBody/index';
|
||||
|
||||
const RequestBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -48,6 +49,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
item={item}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
@@ -61,6 +63,10 @@ const RequestBody = ({ item, collection }) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (bodyMode === 'file') {
|
||||
return <FileBody item={item} collection={collection}/>
|
||||
}
|
||||
|
||||
if (bodyMode === 'formUrlEncoded') {
|
||||
return <FormUrlEncodedParams item={item} collection={collection} />;
|
||||
}
|
||||
@@ -71,4 +77,4 @@ const RequestBody = ({ item, collection }) => {
|
||||
|
||||
return <StyledWrapper className="w-full">No Body</StyledWrapper>;
|
||||
};
|
||||
export default RequestBody;
|
||||
export default RequestBody;
|
||||
@@ -19,15 +19,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-header {
|
||||
|
||||
@@ -4,12 +4,14 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
|
||||
import { addRequestHeader, updateRequestHeader, deleteRequestHeader, moveRequestHeader } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
|
||||
import Table from 'components/Table/index';
|
||||
import ReorderTable from 'components/ReorderTable/index';
|
||||
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||
|
||||
const RequestHeaders = ({ item, collection }) => {
|
||||
@@ -63,22 +65,31 @@ const RequestHeaders = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleHeaderDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveRequestHeader({
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{headers && headers.length
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Key', accessor: 'key', width: '34%' },
|
||||
{ name: 'Value', accessor: 'value', width: '46%' },
|
||||
{ name: '', accessor: '', width: '20%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleHeaderDrag}>
|
||||
{headers && headers.length
|
||||
? headers.map((header) => {
|
||||
return (
|
||||
<tr key={header.uid}>
|
||||
<td>
|
||||
<tr key={header.uid} data-uid={header.uid}>
|
||||
<td className='flex relative'>
|
||||
<SingleLineEditor
|
||||
value={header.name}
|
||||
theme={storedTheme}
|
||||
@@ -140,8 +151,8 @@ const RequestHeaders = ({ item, collection }) => {
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
||||
+ Add Header
|
||||
</button>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.folder-list {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
|
||||
.folder-name {
|
||||
padding-block: 8px;
|
||||
padding-inline: 12px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,55 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { faFolder } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Modal from 'components//Modal';
|
||||
|
||||
const SaveRequest = ({ items, onClose }) => {
|
||||
const [showFolders, setShowFolders] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowFolders(items || []);
|
||||
}, [items]);
|
||||
|
||||
const handleFolderClick = (folder) => {
|
||||
let subFolders = [];
|
||||
if (folder.items && folder.items.length) {
|
||||
for (let item of folder.items) {
|
||||
if (item.items) {
|
||||
subFolders.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (subFolders.length) {
|
||||
setShowFolders(subFolders);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Save Request"
|
||||
confirmText="Save"
|
||||
cancelText="Cancel"
|
||||
handleCancel={onClose}
|
||||
handleConfirm={onClose}
|
||||
>
|
||||
<p className="mb-2">Select a folder to save request:</p>
|
||||
<div className="folder-list">
|
||||
{showFolders && showFolders.length
|
||||
? showFolders.map((folder) => (
|
||||
<div key={folder.uid} className="folder-name" onClick={() => handleFolderClick(folder)}>
|
||||
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{ fontSize: 20 }} />
|
||||
{folder.name}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveRequest;
|
||||
@@ -1,10 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -5,7 +5,6 @@ import CodeEditor from 'components/CodeEditor';
|
||||
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Tests = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -28,19 +27,17 @@ const Tests = ({ item, collection }) => {
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={tests || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={tests || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -19,16 +19,8 @@ const Wrapper = styled.div`
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-var {
|
||||
font-size: 0.8125rem;
|
||||
@@ -38,7 +30,8 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
background-color: inherit;
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
|
||||
@@ -3,13 +3,15 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
|
||||
import { addVar, updateVar, deleteVar, moveVar } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import Table from 'components/Table/index';
|
||||
import ReorderTable from 'components/ReorderTable/index';
|
||||
|
||||
const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -73,35 +75,41 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleVarDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveVar({
|
||||
type: varType,
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
{varType === 'request' ? (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vars && vars.length
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Name', accessor: 'name', width: '40%' },
|
||||
{ name: varType === 'request' ? (
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
</div>
|
||||
), accessor: 'value', width: '46%' },
|
||||
{ name: '', accessor: '', width: '14%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleVarDrag}>
|
||||
{vars && vars.length
|
||||
? vars.map((_var) => {
|
||||
return (
|
||||
<tr key={_var.uid}>
|
||||
<td>
|
||||
<tr key={_var.uid} data-uid={_var.uid}>
|
||||
<td className='flex relative'>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
@@ -152,8 +160,8 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={handleAddVar}>
|
||||
+ Add
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.card {
|
||||
background: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||
|
||||
div.hr {
|
||||
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
div.border-top {
|
||||
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { IconLoader2, IconFile } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestIsLoading = ({ item }) => {
|
||||
return <StyledWrapper>
|
||||
<div className='flex flex-col p-4'>
|
||||
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
|
||||
<div>
|
||||
<div className='font-medium flex items-center gap-2 pb-4'>
|
||||
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
|
||||
File Info
|
||||
</div>
|
||||
<div className='hr'/>
|
||||
|
||||
<div className='flex items-center mt-2'>
|
||||
<span className='w-12 mr-2 text-muted'>Name:</span>
|
||||
<div>
|
||||
{item?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center mt-1'>
|
||||
<span className='w-12 mr-2 text-muted'>Path:</span>
|
||||
<div className='break-all'>
|
||||
{item?.pathname}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center mt-1 pb-4'>
|
||||
<span className='w-12 mr-2 text-muted'>Size:</span>
|
||||
<div>
|
||||
{item?.size?.toFixed?.(2)} MB
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='hr'/>
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
}
|
||||
|
||||
export default RequestIsLoading;
|
||||
@@ -0,0 +1,19 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.card {
|
||||
background: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||
|
||||
div.hr {
|
||||
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
div.border-top {
|
||||
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,89 @@
|
||||
import { IconLoader2, IconFile } from '@tabler/icons';
|
||||
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestNotLoaded = ({ collection, item }) => {
|
||||
const dispatch = useDispatch();
|
||||
const handleLoadRequestViaWorker = () => {
|
||||
!item?.loading && dispatch(loadRequestViaWorker({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||
}
|
||||
|
||||
const handleLoadRequest = () => {
|
||||
!item?.loading && dispatch(loadRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||
}
|
||||
|
||||
return <StyledWrapper>
|
||||
<div className='flex flex-col p-4'>
|
||||
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
|
||||
<div>
|
||||
<div className='font-medium flex items-center gap-2 pb-4'>
|
||||
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
|
||||
File Info
|
||||
</div>
|
||||
<div className='hr'/>
|
||||
|
||||
<div className='flex items-center mt-2'>
|
||||
<span className='w-12 mr-2 text-muted'>Name:</span>
|
||||
<div>{item?.name}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center mt-1'>
|
||||
<span className='w-12 mr-2 text-muted'>Path:</span>
|
||||
<div className='break-all'>{item?.pathname}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center mt-1 pb-4'>
|
||||
<span className='w-12 mr-2 text-muted'>Size:</span>
|
||||
<div>{item?.size?.toFixed?.(2)} MB</div>
|
||||
</div>
|
||||
|
||||
{!item?.error && (
|
||||
<>
|
||||
<div className='hr'/>
|
||||
<div className='text-muted text-xs mt-4 mb-2'>
|
||||
Due to its large size, this request wasn't loaded automatically.
|
||||
</div>
|
||||
<div className='flex flex-col gap-6 mt-4'>
|
||||
<div className='flex flex-col'>
|
||||
<button
|
||||
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||
onClick={handleLoadRequest}
|
||||
>
|
||||
Load Request
|
||||
</button>
|
||||
<small className='text-muted mt-1'>
|
||||
May cause the app to freeze temporarily while it runs.
|
||||
</small>
|
||||
</div>
|
||||
<div className='flex flex-col'>
|
||||
<button
|
||||
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||
onClick={handleLoadRequestViaWorker}
|
||||
>
|
||||
Load Request in Background
|
||||
</button>
|
||||
<small className='text-muted mt-1'>
|
||||
Runs in background.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{item?.loading && (
|
||||
<>
|
||||
<div className='hr mt-4'/>
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
}
|
||||
|
||||
export default RequestNotLoaded;
|
||||
@@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 10px;
|
||||
min-width: 10px;
|
||||
padding: 0;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
|
||||
@@ -20,8 +20,11 @@ import { DocExplorer } from '@usebruno/graphql-docs';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SecuritySettings from 'components/SecuritySettings';
|
||||
import FolderSettings from 'components/FolderSettings';
|
||||
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index';
|
||||
import { produce } from 'immer';
|
||||
import CollectionOverview from 'components/CollectionSettings/Overview';
|
||||
import RequestNotLoaded from './RequestNotLoaded';
|
||||
import RequestIsLoading from './RequestIsLoading';
|
||||
|
||||
const MIN_LEFT_PANE_WIDTH = 300;
|
||||
const MIN_RIGHT_PANE_WIDTH = 350;
|
||||
@@ -34,12 +37,30 @@ const RequestTabPanel = () => {
|
||||
const dispatch = useDispatch();
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
const screenWidth = useSelector((state) => state.app.screenWidth);
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
const _collections = useSelector((state) => state.collections.collections);
|
||||
|
||||
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
|
||||
let collections = produce(_collections, (draft) => {
|
||||
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
|
||||
|
||||
if (collection) {
|
||||
// add selected global env variables to the collection object
|
||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
|
||||
globalEnvironments,
|
||||
activeGlobalEnvironmentUid
|
||||
});
|
||||
const globalEnvSecrets = getGlobalEnvironmentVariablesMasked({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||
collection.globalEnvSecrets = globalEnvSecrets;
|
||||
}
|
||||
});
|
||||
|
||||
let collection = find(collections, (c) => c.uid === focusedTab?.collectionUid);
|
||||
|
||||
const screenWidth = useSelector((state) => state.app.screenWidth);
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(
|
||||
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
|
||||
); // 2.2 so that request pane is relatively smaller
|
||||
@@ -120,14 +141,6 @@ const RequestTabPanel = () => {
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
let _collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
|
||||
let collection = cloneDeep(_collection);
|
||||
|
||||
// add selected global env variables to the collection object
|
||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||
|
||||
|
||||
if (!collection || !collection.uid) {
|
||||
return <div className="pb-4 px-4">Collection not found!</div>;
|
||||
}
|
||||
@@ -143,6 +156,11 @@ const RequestTabPanel = () => {
|
||||
if (focusedTab.type === 'collection-settings') {
|
||||
return <CollectionSettings collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'collection-overview') {
|
||||
return <CollectionOverview collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'folder-settings') {
|
||||
const folder = findItemInCollection(collection, focusedTab.folderUid);
|
||||
return <FolderSettings collection={collection} folder={folder} />;
|
||||
@@ -157,6 +175,14 @@ const RequestTabPanel = () => {
|
||||
return <RequestNotFound itemUid={activeTabUid} />;
|
||||
}
|
||||
|
||||
if (item?.partial) {
|
||||
return <RequestNotLoaded item={item} collection={collection} />
|
||||
}
|
||||
|
||||
if (item?.loading) {
|
||||
return <RequestIsLoading item={item} />
|
||||
}
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user