mirror of
https://github.com/usebruno/bruno.git
synced 2025-03-15 07:18:29 +01:00
Merge branch 'usebruno:main' into feature/add-raw-file-request-body-option
This commit is contained in:
commit
069a038f61
16
.github/workflows/tests.yml
vendored
16
.github/workflows/tests.yml
vendored
@ -15,6 +15,8 @@ jobs:
|
|||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: './package-lock.json'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
|
|
||||||
@ -50,6 +52,8 @@ jobs:
|
|||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: './package-lock.json'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
@ -71,15 +75,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: packages/bruno-tests/collection/junit.xml
|
files: packages/bruno-tests/collection/junit.xml
|
||||||
comment_mode: always
|
comment_mode: always
|
||||||
prettier:
|
|
||||||
name: Prettier
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci --legacy-peer-deps
|
|
||||||
- name: Run Prettier
|
|
||||||
run: npm run test:prettier:web
|
|
||||||
|
@ -62,6 +62,9 @@ npm run build:graphql-docs
|
|||||||
# construction de bruno query
|
# construction de bruno query
|
||||||
npm run build:bruno-query
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# construction de bruno common
|
||||||
|
npm run build:bruno-common
|
||||||
|
|
||||||
# démarrage de next (terminal 1)
|
# démarrage de next (terminal 1)
|
||||||
npm run dev:web
|
npm run dev:web
|
||||||
|
|
||||||
|
84
docs/contributing/contributing_sk.md
Normal file
84
docs/contributing/contributing_sk.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
## Urobme bruno lepším, spoločne !!
|
||||||
|
|
||||||
|
Sme radi, že chcete zlepšiť bruno. Nižšie sú uvedené pokyny, ako začať s výchovou bruno na vašom počítači.
|
||||||
|
|
||||||
|
### Technologický zásobník
|
||||||
|
|
||||||
|
Bruno je vytvorené pomocou Next.js a React. Na dodávanie desktopovej verzie (ktorá podporuje lokálne kolekcie) používame aj electron.
|
||||||
|
|
||||||
|
Balíčky, ktoré používame:
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Editory kódu - Codemirror
|
||||||
|
- Správa stavu - Redux
|
||||||
|
- Ikony - Tabler Icons
|
||||||
|
- Formuláre - formik
|
||||||
|
- Overovanie schém - Yup
|
||||||
|
- Klient požiadaviek - axios
|
||||||
|
- Sledovač súborového systému - chokidar
|
||||||
|
|
||||||
|
### Závislosti
|
||||||
|
|
||||||
|
Budete potrebovať [NodeJS v18.x alebo najnovšiu verziu LTS](https://nodejs.org/en/) a npm versiu 8.x. V projekte používame pracovné priestory npm
|
||||||
|
|
||||||
|
## Vývoj
|
||||||
|
|
||||||
|
Bruno sa vyvíja ako desktopová aplikácia. Aplikáciu je potrebné načítať spustením aplikácie Next.js v jednom termináli a potom spustiť aplikáciu electron v inom termináli.
|
||||||
|
|
||||||
|
### Závislosti
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Miestny vývoj
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# použite verziu nodejs 18
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# nainštalovať balíčky
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# zostaviť balíčky
|
||||||
|
npm run build:graphql-docs
|
||||||
|
npm run build:bruno-query
|
||||||
|
npm run build:bruno-common
|
||||||
|
|
||||||
|
# spustite ďalšiu aplikáciu (terminál 1)
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# spustite aplikáciu electron (terminál 2)
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Riešenie problémov
|
||||||
|
|
||||||
|
Pri spustení `npm install` sa môžete stretnúť s chybou `Unsupported platform`. Ak chcete túto chybu odstrániť, musíte odstrániť súbory `node_modules`, `package-lock.json` a spustiť `npm install`. Tým by sa mali nainštalovať všetky potrebné balíky potrebné na spustenie aplikácie.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Odstrániť node_modules v podadresároch
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Odstráňte package-lock v podadresároch
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testovanie
|
||||||
|
|
||||||
|
````bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vyrobenie Pull Request
|
||||||
|
|
||||||
|
- Prosím, aby PR boli malé a zamerané na jednu vec
|
||||||
|
- Prosím, dodržujte formát vytvárania vetiev
|
||||||
|
- feature/[názov funkcie]: Táto vetva by mala obsahovať zmeny pre konkrétnu funkciu
|
||||||
|
- Príklad: feature/dark-mode
|
||||||
|
- bugfix/[názov chyby]: Táto vetva by mala obsahovať iba opravy konkrétnej chyby
|
||||||
|
- Príklad: bugfix/bug-1
|
@ -103,6 +103,12 @@ Ou qualquer sistema de controle de versão de sua escolha.
|
|||||||
|
|
||||||
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
|
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
|
||||||
|
|
||||||
|
#### Apoiadores Bronze
|
||||||
|
|
||||||
|
<a href="https://zuplo.link/bruno">
|
||||||
|
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
### Links Importantes 📌
|
### Links Importantes 📌
|
||||||
|
|
||||||
- [Nossa Visão de Longo Prazo](https://github.com/usebruno/bruno/discussions/269)
|
- [Nossa Visão de Longo Prazo](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -19668,7 +19668,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v1.21.0",
|
"version": "v1.23.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "3.525.0",
|
"@aws-sdk/credential-providers": "3.525.0",
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
|
@ -6,6 +6,7 @@ const StyledWrapper = styled.div`
|
|||||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||||
font-family: ${(props) => (props.font ? props.font : 'default')};
|
font-family: ${(props) => (props.font ? props.font : 'default')};
|
||||||
line-break: anywhere;
|
line-break: anywhere;
|
||||||
|
flex: 1 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-overlayscroll-horizontal div,
|
.CodeMirror-overlayscroll-horizontal div,
|
||||||
|
@ -16,6 +16,7 @@ import stripJsonComments from 'strip-json-comments';
|
|||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
|
const TAB_SIZE = 2;
|
||||||
|
|
||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
CodeMirror = require('codemirror');
|
CodeMirror = require('codemirror');
|
||||||
@ -121,7 +122,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
value: this.props.value || '',
|
value: this.props.value || '',
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
tabSize: 2,
|
tabSize: TAB_SIZE,
|
||||||
mode: this.props.mode || 'application/ld+json',
|
mode: this.props.mode || 'application/ld+json',
|
||||||
keyMap: 'sublime',
|
keyMap: 'sublime',
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
@ -169,7 +170,33 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Ctrl-Y': 'foldAll',
|
'Ctrl-Y': 'foldAll',
|
||||||
'Cmd-Y': 'foldAll',
|
'Cmd-Y': 'foldAll',
|
||||||
'Ctrl-I': 'unfoldAll',
|
'Ctrl-I': 'unfoldAll',
|
||||||
'Cmd-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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
foldOptions: {
|
foldOptions: {
|
||||||
widget: (from, to) => {
|
widget: (from, to) => {
|
||||||
@ -289,7 +316,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
className="h-full w-full"
|
className="h-full w-full flex flex-col relative"
|
||||||
aria-label="Code Editor"
|
aria-label="Code Editor"
|
||||||
font={this.props.font}
|
font={this.props.font}
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
|
@ -138,6 +138,7 @@ const AwsV4Auth = ({ collection }) => {
|
|||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onChange={(val) => handleSecretAccessKeyChange(val)}
|
onChange={(val) => handleSecretAccessKeyChange(val)}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ const BasicAuth = ({ collection }) => {
|
|||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onChange={(val) => handlePasswordChange(val)}
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -37,6 +37,7 @@ const BearerAuth = ({ collection }) => {
|
|||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onChange={(val) => handleTokenChange(val)}
|
onChange={(val) => handleTokenChange(val)}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -62,6 +62,7 @@ const DigestAuth = ({ collection }) => {
|
|||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onChange={(val) => handlePasswordChange(val)}
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -78,7 +78,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -90,6 +90,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
|
|||||||
onChange={(val) => handleChange(key, val)}
|
onChange={(val) => handleChange(key, val)}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -42,7 +42,7 @@ const OAuth2ClientCredentials = ({ collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -54,6 +54,7 @@ const OAuth2ClientCredentials = ({ collection }) => {
|
|||||||
onChange={(val) => handleChange(key, val)}
|
onChange={(val) => handleChange(key, val)}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -44,7 +44,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -56,6 +56,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
onChange={(val) => handleChange(key, val)}
|
onChange={(val) => handleChange(key, val)}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -74,6 +74,7 @@ const PresetsSettings = ({ collection }) => {
|
|||||||
id="request-url"
|
id="request-url"
|
||||||
type="text"
|
type="text"
|
||||||
name="requestUrl"
|
name="requestUrl"
|
||||||
|
placeholder='Request URL'
|
||||||
className="block textbox"
|
className="block textbox"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
div.CodeMirror {
|
|
||||||
/* todo: find a better way */
|
|
||||||
height: calc(100vh - 240px);
|
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
|
||||||
padding-bottom: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.editing-mode {
|
.editing-mode {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${(props) => props.theme.colors.text.yellow};
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
@ -37,8 +37,8 @@ const Documentation = ({ item, collection }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-1 h-full w-full relative">
|
<StyledWrapper className="flex flex-col gap-y-1 h-full w-full relative">
|
||||||
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
|
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
|
||||||
{isEditing ? 'Preview' : 'Edit'}
|
{isEditing ? 'Preview' : 'Edit'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux';
|
|||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import { maskInputValue } from 'utils/collections';
|
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { variableNameRegex } from 'utils/common/regex';
|
import { variableNameRegex } from 'utils/common/regex';
|
||||||
@ -96,10 +95,10 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Enabled</td>
|
<td className="text-center">Enabled</td>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>Value</td>
|
<td>Value</td>
|
||||||
<td>Secret</td>
|
<td className="text-center">Secret</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -109,7 +108,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="mr-3 mousetrap"
|
className="mousetrap"
|
||||||
name={`${index}.enabled`}
|
name={`${index}.enabled`}
|
||||||
checked={variable.enabled}
|
checked={variable.enabled}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
@ -130,23 +129,22 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
/>
|
/>
|
||||||
<ErrorMessage name={`${index}.name`} />
|
<ErrorMessage name={`${index}.name`} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="flex flex-row flex-nowrap">
|
||||||
{variable.secret ? (
|
<div className="overflow-hidden grow w-full relative">
|
||||||
<div className="overflow-hidden text-ellipsis">{maskInputValue(variable.value)}</div>
|
|
||||||
) : (
|
|
||||||
<SingleLineEditor
|
<SingleLineEditor
|
||||||
theme={storedTheme}
|
theme={storedTheme}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
name={`${index}.value`}
|
name={`${index}.value`}
|
||||||
value={variable.value}
|
value={variable.value}
|
||||||
|
isSecret={variable.secret}
|
||||||
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="text-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="mr-3 mousetrap"
|
className="mousetrap"
|
||||||
name={`${index}.secret`}
|
name={`${index}.secret`}
|
||||||
checked={variable.secret}
|
checked={variable.secret}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
|
@ -2,7 +2,6 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const StyledMarkdownBodyWrapper = styled.div`
|
const StyledMarkdownBodyWrapper = styled.div`
|
||||||
background: transparent;
|
background: transparent;
|
||||||
height: inherit;
|
|
||||||
.markdown-body {
|
.markdown-body {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -150,6 +150,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ const BasicAuth = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -43,6 +43,7 @@ const BearerAuth = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -69,6 +69,7 @@ const DigestAuth = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -80,7 +80,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -93,6 +93,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -43,7 +43,7 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -56,6 +56,7 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -45,7 +45,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||||
{inputsConfig.map((input) => {
|
{inputsConfig.map((input) => {
|
||||||
const { key, label } = input;
|
const { key, label, isSecret } = input;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||||
<label className="block font-medium">{label}</label>
|
<label className="block font-medium">{label}</label>
|
||||||
@ -58,6 +58,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
|||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
|
isSecret={isSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'password',
|
key: 'password',
|
||||||
label: 'Password'
|
label: 'Password',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientId',
|
key: 'clientId',
|
||||||
@ -17,7 +18,8 @@ const inputsConfig = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'clientSecret',
|
key: 'clientSecret',
|
||||||
label: 'Client Secret'
|
label: 'Client Secret',
|
||||||
|
isSecret: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'scope',
|
key: 'scope',
|
||||||
|
@ -137,7 +137,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<section
|
<section
|
||||||
className={classnames('flex w-full', {
|
className={classnames('flex w-full flex-1', {
|
||||||
'mt-5': !isMultipleContentTab
|
'mt-5': !isMultipleContentTab
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
@ -4,6 +4,7 @@ const StyledWrapper = styled.div`
|
|||||||
div.CodeMirror {
|
div.CodeMirror {
|
||||||
background: ${(props) => props.theme.codemirror.bg};
|
background: ${(props) => props.theme.codemirror.bg};
|
||||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||||
|
flex: 1 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.cm-editor {
|
textarea.cm-editor {
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
div.CodeMirror {
|
|
||||||
/* todo: find a better way */
|
|
||||||
height: calc(100vh - 220px);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
@ -40,8 +40,8 @@ const Script = ({ item, collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full flex flex-col">
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
<div className="flex-1 mt-2">
|
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||||
<div className="mb-1 title text-xs">Pre Request</div>
|
<div className="title text-xs">Pre Request</div>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
collection={collection}
|
collection={collection}
|
||||||
value={requestScript || ''}
|
value={requestScript || ''}
|
||||||
@ -53,8 +53,8 @@ const Script = ({ item, collection }) => {
|
|||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 mt-6">
|
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
<div className="title text-xs">Post Response</div>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
collection={collection}
|
collection={collection}
|
||||||
value={responseScript || ''}
|
value={responseScript || ''}
|
||||||
|
@ -9,11 +9,11 @@ const Vars = ({ item, collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full flex flex-col">
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
<div className="flex-1 mt-2">
|
<div className="mt-2">
|
||||||
<div className="mb-1 title text-xs">Pre Request</div>
|
<div className="mb-1 title text-xs">Pre Request</div>
|
||||||
<VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
|
<VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div>
|
||||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||||
<VarsTable item={item} collection={collection} vars={responseVars} varType="response" />
|
<VarsTable item={item} collection={collection} vars={responseVars} varType="response" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -158,10 +158,9 @@ const RequestTabPanel = () => {
|
|||||||
<section className="main flex flex-grow pb-4 relative">
|
<section className="main flex flex-grow pb-4 relative">
|
||||||
<section className="request-pane">
|
<section className="request-pane">
|
||||||
<div
|
<div
|
||||||
className="px-4"
|
className="px-4 h-full"
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`,
|
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`
|
||||||
height: `calc(100% - ${DEFAULT_PADDING}px)`
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.type === 'graphql-request' ? (
|
{item.type === 'graphql-request' ? (
|
||||||
|
@ -14,6 +14,8 @@ const Wrapper = styled.div`
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
|
.textbox {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
transition: border-color ease-in-out 0.1s;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: ${(props) => props.theme.modal.input.bg};
|
||||||
|
border: 1px solid ${(props) => props.theme.modal.input.border};
|
||||||
|
}
|
||||||
|
|
||||||
.item-path {
|
.item-path {
|
||||||
.link {
|
.link {
|
||||||
color: ${(props) => props.theme.textLink};
|
color: ${(props) => props.theme.textLink};
|
||||||
|
@ -23,6 +23,7 @@ const getRelativePath = (fullPath, pathname) => {
|
|||||||
export default function RunnerResults({ collection }) {
|
export default function RunnerResults({ collection }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [selectedItem, setSelectedItem] = useState(null);
|
const [selectedItem, setSelectedItem] = useState(null);
|
||||||
|
const [delay, setDelay] = useState(null);
|
||||||
|
|
||||||
// ref for the runner output body
|
// ref for the runner output body
|
||||||
const runnerBodyRef = useRef();
|
const runnerBodyRef = useRef();
|
||||||
@ -78,11 +79,11 @@ export default function RunnerResults({ collection }) {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const runCollection = () => {
|
const runCollection = () => {
|
||||||
dispatch(runCollectionFolder(collection.uid, null, true));
|
dispatch(runCollectionFolder(collection.uid, null, true, Number(delay)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const runAgain = () => {
|
const runAgain = () => {
|
||||||
dispatch(runCollectionFolder(collection.uid, runnerInfo.folderUid, runnerInfo.isRecursive));
|
dispatch(runCollectionFolder(collection.uid, runnerInfo.folderUid, runnerInfo.isRecursive, Number(delay)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetRunner = () => {
|
const resetRunner = () => {
|
||||||
@ -116,6 +117,20 @@ export default function RunnerResults({ collection }) {
|
|||||||
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<label>Delay (in ms)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="block textbox mt-2 py-5"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
value={delay}
|
||||||
|
onChange={(e) => setDelay(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runCollection}>
|
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runCollection}>
|
||||||
Run Collection
|
Run Collection
|
||||||
</button>
|
</button>
|
||||||
@ -167,10 +182,14 @@ export default function RunnerResults({ collection }) {
|
|||||||
</span>
|
</span>
|
||||||
{item.status !== 'error' && item.status !== 'completed' ? (
|
{item.status !== 'error' && item.status !== 'completed' ? (
|
||||||
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
|
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
|
||||||
) : (
|
) : item.responseReceived?.status ? (
|
||||||
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
|
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
|
||||||
(<span className="mr-1">{get(item.responseReceived, 'status')}</span>
|
(<span className="mr-1">{item.responseReceived?.status}</span>
|
||||||
<span>{get(item.responseReceived, 'statusText')}</span>)
|
<span>{item.responseReceived?.statusText}</span>)
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}>
|
||||||
|
(request failed)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,6 +58,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
id="collection-item-name"
|
id="collection-item-name"
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
|
placeholder='Enter Item name'
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="block textbox mt-2 w-full"
|
className="block textbox mt-2 w-full"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
@ -2,6 +2,7 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.copy-to-clipboard {
|
.copy-to-clipboard {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -91,13 +91,13 @@ const Collections = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
|
placeholder="search"
|
||||||
id="search"
|
id="search"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className="block w-full pl-7 py-1 sm:text-sm"
|
className="block w-full pl-7 py-1 sm:text-sm"
|
||||||
placeholder="search"
|
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
|
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
|
||||||
/>
|
/>
|
||||||
|
@ -160,7 +160,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName, trans
|
|||||||
type="text"
|
type="text"
|
||||||
name="collectionLocation"
|
name="collectionLocation"
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
className="block textbox mt-2 w-full"
|
className="block textbox mt-2 w-full cursor-pointer"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
|
@ -161,7 +161,16 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
<form
|
||||||
|
className="bruno-form"
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
formik.handleSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="requestName" className="block font-semibold">
|
<label htmlFor="requestName" className="block font-semibold">
|
||||||
Type
|
Type
|
||||||
@ -220,6 +229,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
id="request-name"
|
id="request-name"
|
||||||
type="text"
|
type="text"
|
||||||
name="requestName"
|
name="requestName"
|
||||||
|
placeholder="Request Name"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="block textbox mt-2 w-full"
|
className="block textbox mt-2 w-full"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -252,6 +262,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
id="request-url"
|
id="request-url"
|
||||||
type="text"
|
type="text"
|
||||||
name="requestUrl"
|
name="requestUrl"
|
||||||
|
placeholder="Request URL"
|
||||||
className="px-3 w-full "
|
className="px-3 w-full "
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
|
@ -129,7 +129,7 @@ const Sidebar = () => {
|
|||||||
Star
|
Star
|
||||||
</GitHubButton> */}
|
</GitHubButton> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.22.0</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.24.0</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import { getAllVariables } from 'utils/collections';
|
import { getAllVariables } from 'utils/collections';
|
||||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
import { defineCodeMirrorBrunoVariablesMode, MaskedEditor } from 'utils/common/codemirror';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { IconEye, IconEyeOff } from '@tabler/icons';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
@ -20,12 +21,28 @@ class SingleLineEditor extends Component {
|
|||||||
this.cachedValue = props.value || '';
|
this.cachedValue = props.value || '';
|
||||||
this.editorRef = React.createRef();
|
this.editorRef = React.createRef();
|
||||||
this.variables = {};
|
this.variables = {};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
maskInput: props.isSecret || false // Always mask the input by default (if it's a secret)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Initialize CodeMirror as a single line editor
|
// Initialize CodeMirror as a single line editor
|
||||||
/** @type {import("codemirror").Editor} */
|
/** @type {import("codemirror").Editor} */
|
||||||
const variables = getAllVariables(this.props.collection, this.props.item);
|
const variables = getAllVariables(this.props.collection, this.props.item);
|
||||||
|
|
||||||
|
const runHandler = () => {
|
||||||
|
if (this.props.onRun) {
|
||||||
|
this.props.onRun();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const saveHandler = () => {
|
||||||
|
if (this.props.onSave) {
|
||||||
|
this.props.onSave();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const noopHandler = () => {};
|
||||||
|
|
||||||
this.editor = CodeMirror(this.editorRef.current, {
|
this.editor = CodeMirror(this.editorRef.current, {
|
||||||
lineWrapping: false,
|
lineWrapping: false,
|
||||||
lineNumbers: false,
|
lineNumbers: false,
|
||||||
@ -37,21 +54,9 @@ class SingleLineEditor extends Component {
|
|||||||
scrollbarStyle: null,
|
scrollbarStyle: null,
|
||||||
tabindex: 0,
|
tabindex: 0,
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Enter: () => {
|
Enter: runHandler,
|
||||||
if (this.props.onRun) {
|
'Ctrl-Enter': runHandler,
|
||||||
this.props.onRun();
|
'Cmd-Enter': runHandler,
|
||||||
}
|
|
||||||
},
|
|
||||||
'Ctrl-Enter': () => {
|
|
||||||
if (this.props.onRun) {
|
|
||||||
this.props.onRun();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Cmd-Enter': () => {
|
|
||||||
if (this.props.onRun) {
|
|
||||||
this.props.onRun();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Alt-Enter': () => {
|
'Alt-Enter': () => {
|
||||||
if (this.props.allowNewlines) {
|
if (this.props.allowNewlines) {
|
||||||
this.editor.setValue(this.editor.getValue() + '\n');
|
this.editor.setValue(this.editor.getValue() + '\n');
|
||||||
@ -60,23 +65,11 @@ class SingleLineEditor extends Component {
|
|||||||
this.props.onRun();
|
this.props.onRun();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Shift-Enter': () => {
|
'Shift-Enter': runHandler,
|
||||||
if (this.props.onRun) {
|
'Cmd-S': saveHandler,
|
||||||
this.props.onRun();
|
'Ctrl-S': saveHandler,
|
||||||
}
|
'Cmd-F': noopHandler,
|
||||||
},
|
'Ctrl-F': noopHandler,
|
||||||
'Cmd-S': () => {
|
|
||||||
if (this.props.onSave) {
|
|
||||||
this.props.onSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Ctrl-S': () => {
|
|
||||||
if (this.props.onSave) {
|
|
||||||
this.props.onSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Cmd-F': () => {},
|
|
||||||
'Ctrl-F': () => {},
|
|
||||||
// Tabbing disabled to make tabindex work
|
// Tabbing disabled to make tabindex work
|
||||||
Tab: false,
|
Tab: false,
|
||||||
'Shift-Tab': false
|
'Shift-Tab': false
|
||||||
@ -93,8 +86,24 @@ class SingleLineEditor extends Component {
|
|||||||
this.editor.setValue(String(this.props.value) || '');
|
this.editor.setValue(String(this.props.value) || '');
|
||||||
this.editor.on('change', this._onEdit);
|
this.editor.on('change', this._onEdit);
|
||||||
this.addOverlay(variables);
|
this.addOverlay(variables);
|
||||||
|
this._enableMaskedEditor(this.props.isSecret);
|
||||||
|
this.setState({ maskInput: this.props.isSecret });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Enable or disable masking the rendered content of the editor */
|
||||||
|
_enableMaskedEditor = (enabled) => {
|
||||||
|
if (typeof enabled !== 'boolean') return;
|
||||||
|
|
||||||
|
console.log('Enabling masked editor: ' + enabled);
|
||||||
|
if (enabled == true) {
|
||||||
|
if (!this.maskedEditor) this.maskedEditor = new MaskedEditor(this.editor, '*');
|
||||||
|
this.maskedEditor.enable();
|
||||||
|
} else {
|
||||||
|
this.maskedEditor?.disable();
|
||||||
|
this.maskedEditor = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_onEdit = () => {
|
_onEdit = () => {
|
||||||
if (!this.ignoreChangeEvent && this.editor) {
|
if (!this.ignoreChangeEvent && this.editor) {
|
||||||
this.cachedValue = this.editor.getValue();
|
this.cachedValue = this.editor.getValue();
|
||||||
@ -122,6 +131,12 @@ class SingleLineEditor extends Component {
|
|||||||
this.cachedValue = String(this.props.value);
|
this.cachedValue = String(this.props.value);
|
||||||
this.editor.setValue(String(this.props.value) || '');
|
this.editor.setValue(String(this.props.value) || '');
|
||||||
}
|
}
|
||||||
|
if (!isEqual(this.props.isSecret, prevProps.isSecret)) {
|
||||||
|
// If the secret flag has changed, update the editor to reflect the change
|
||||||
|
this._enableMaskedEditor(this.props.isSecret);
|
||||||
|
// also set the maskInput flag to the new value
|
||||||
|
this.setState({ maskInput: this.props.isSecret });
|
||||||
|
}
|
||||||
this.ignoreChangeEvent = false;
|
this.ignoreChangeEvent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,8 +150,35 @@ class SingleLineEditor extends Component {
|
|||||||
this.editor.setOption('mode', 'brunovariables');
|
this.editor.setOption('mode', 'brunovariables');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleVisibleSecret = () => {
|
||||||
|
const isVisible = !this.state.maskInput;
|
||||||
|
this.setState({ maskInput: isVisible });
|
||||||
|
this._enableMaskedEditor(isVisible);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Eye icon to show/hide the secret value
|
||||||
|
* @returns ReactComponent The eye icon
|
||||||
|
*/
|
||||||
|
secretEye = (isSecret) => {
|
||||||
|
return isSecret === true ? (
|
||||||
|
<button className="mx-2" onClick={() => this.toggleVisibleSecret()}>
|
||||||
|
{this.state.maskInput === true ? (
|
||||||
|
<IconEyeOff size={18} strokeWidth={2} />
|
||||||
|
) : (
|
||||||
|
<IconEye size={18} strokeWidth={2} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <StyledWrapper ref={this.editorRef} className="single-line-editor"></StyledWrapper>;
|
return (
|
||||||
|
<div className="flex flex-row justify-between w-full">
|
||||||
|
<StyledWrapper ref={this.editorRef} className="single-line-editor grow" />
|
||||||
|
{this.secretEye(this.props.isSecret)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default SingleLineEditor;
|
export default SingleLineEditor;
|
||||||
|
@ -100,6 +100,11 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: ${(props) => props.theme.input.placeholder.color};
|
||||||
|
opacity: ${(props) => props.theme.input.placeholder.opacity};
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { get } from 'lodash';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
|
||||||
import ConfirmAppClose from './ConfirmAppClose';
|
import ConfirmAppClose from './ConfirmAppClose';
|
||||||
@ -18,6 +19,13 @@ export const AppProvider = (props) => {
|
|||||||
dispatch(refreshScreenWidth());
|
dispatch(refreshScreenWidth());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const platform = get(navigator, 'platform', '');
|
||||||
|
if(platform && platform.toLowerCase().indexOf('mac') > -1) {
|
||||||
|
document.body.classList.add('os-mac');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
dispatch(refreshScreenWidth());
|
dispatch(refreshScreenWidth());
|
||||||
|
@ -60,7 +60,7 @@ const trackStart = () => {
|
|||||||
event: 'start',
|
event: 'start',
|
||||||
properties: {
|
properties: {
|
||||||
os: platformLib.os.family,
|
os: platformLib.os.family,
|
||||||
version: '1.22.0'
|
version: '1.24.0'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -192,10 +192,7 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
|
|||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
||||||
|
|
||||||
const externalSecrets = getExternalCollectionSecretsForActiveEnvironment({ collection });
|
_sendCollectionOauth2Request(collection, environment, collectionCopy.runtimeVariables)
|
||||||
const secretVariables = getFormattedCollectionSecretVariables({ externalSecrets });
|
|
||||||
|
|
||||||
_sendCollectionOauth2Request(collection, environment, collectionCopy.runtimeVariables, itemUid, secretVariables)
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response?.data?.error) {
|
if (response?.data?.error) {
|
||||||
toast.error(response?.data?.error);
|
toast.error(response?.data?.error);
|
||||||
@ -284,7 +281,7 @@ export const cancelRunnerExecution = (cancelTokenUid) => (dispatch) => {
|
|||||||
cancelNetworkRequest(cancelTokenUid).catch((err) => console.log(err));
|
cancelNetworkRequest(cancelTokenUid).catch((err) => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => {
|
export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
@ -315,7 +312,8 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dis
|
|||||||
collectionCopy,
|
collectionCopy,
|
||||||
environment,
|
environment,
|
||||||
collectionCopy.runtimeVariables,
|
collectionCopy.runtimeVariables,
|
||||||
recursive
|
recursive,
|
||||||
|
delay
|
||||||
)
|
)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -58,6 +58,15 @@ body::-webkit-scrollbar-thumb,
|
|||||||
border-radius: 5rem;
|
border-radius: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mac-specific scrollbar styling
|
||||||
|
* This ensures that scrollbars are only visible when the user starts to scroll,
|
||||||
|
* providing a cleaner and more minimalistic appearance.
|
||||||
|
*/
|
||||||
|
body.os-mac * {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* todo: this will be supported in the future to be changed via applying a theme
|
* todo: this will be supported in the future to be changed via applying a theme
|
||||||
* making all the checkboxes and radios bigger
|
* making all the checkboxes and radios bigger
|
||||||
|
@ -20,7 +20,11 @@ const darkTheme = {
|
|||||||
input: {
|
input: {
|
||||||
bg: 'rgb(65, 65, 65)',
|
bg: 'rgb(65, 65, 65)',
|
||||||
border: 'rgb(65, 65, 65)',
|
border: 'rgb(65, 65, 65)',
|
||||||
focusBorder: 'rgb(65, 65, 65)'
|
focusBorder: 'rgb(65, 65, 65)',
|
||||||
|
placeholder: {
|
||||||
|
color: '#a2a2a2',
|
||||||
|
opacity: 0.75
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
variables: {
|
variables: {
|
||||||
@ -154,7 +158,7 @@ const darkTheme = {
|
|||||||
modal: {
|
modal: {
|
||||||
title: {
|
title: {
|
||||||
color: '#ccc',
|
color: '#ccc',
|
||||||
bg: 'rgb(48, 48, 49)',
|
bg: 'rgb(38, 38, 39)',
|
||||||
iconColor: '#ccc'
|
iconColor: '#ccc'
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
|
@ -20,7 +20,11 @@ const lightTheme = {
|
|||||||
input: {
|
input: {
|
||||||
bg: 'white',
|
bg: 'white',
|
||||||
border: '#ccc',
|
border: '#ccc',
|
||||||
focusBorder: '#8b8b8b'
|
focusBorder: '#8b8b8b',
|
||||||
|
placeholder: {
|
||||||
|
color: '#a2a2a2',
|
||||||
|
opacity: 0.8
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
menubar: {
|
menubar: {
|
||||||
|
@ -2,10 +2,16 @@ const createContentType = (mode) => {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'json':
|
case 'json':
|
||||||
return 'application/json';
|
return 'application/json';
|
||||||
|
case 'text':
|
||||||
|
return 'text/plain';
|
||||||
case 'xml':
|
case 'xml':
|
||||||
return 'application/xml';
|
return 'application/xml';
|
||||||
|
case 'sparql':
|
||||||
|
return 'application/sparql-query';
|
||||||
case 'formUrlEncoded':
|
case 'formUrlEncoded':
|
||||||
return 'application/x-www-form-urlencoded';
|
return 'application/x-www-form-urlencoded';
|
||||||
|
case 'graphql':
|
||||||
|
return 'application/json';
|
||||||
case 'multipartForm':
|
case 'multipartForm':
|
||||||
return 'multipart/form-data';
|
return 'multipart/form-data';
|
||||||
default:
|
default:
|
||||||
@ -13,13 +19,19 @@ const createContentType = (mode) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createHeaders = (headers) => {
|
const createHeaders = (request, headers) => {
|
||||||
return headers
|
const enabledHeaders = headers
|
||||||
.filter((header) => header.enabled)
|
.filter((header) => header.enabled)
|
||||||
.map((header) => ({
|
.map((header) => ({
|
||||||
name: header.name,
|
name: header.name,
|
||||||
value: header.value
|
value: header.value
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const contentType = createContentType(request.body?.mode);
|
||||||
|
if (contentType !== '') {
|
||||||
|
enabledHeaders.push({ name: 'content-type', value: contentType });
|
||||||
|
}
|
||||||
|
return enabledHeaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createQuery = (queryParams = []) => {
|
const createQuery = (queryParams = []) => {
|
||||||
@ -54,7 +66,7 @@ export const buildHarRequest = ({ request, headers }) => {
|
|||||||
url: encodeURI(request.url),
|
url: encodeURI(request.url),
|
||||||
httpVersion: 'HTTP/1.1',
|
httpVersion: 'HTTP/1.1',
|
||||||
cookies: [],
|
cookies: [],
|
||||||
headers: createHeaders(headers),
|
headers: createHeaders(request, headers),
|
||||||
queryString: createQuery(request.params),
|
queryString: createQuery(request.params),
|
||||||
postData: createPostData(request.body),
|
postData: createPostData(request.body),
|
||||||
headersSize: 0,
|
headersSize: 0,
|
||||||
|
@ -12,6 +12,64 @@ const pathFoundInVariables = (path, obj) => {
|
|||||||
return value !== undefined;
|
return value !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the render behaviour for a given CodeMirror editor.
|
||||||
|
* Replaces all **rendered** characters, not the actual value, with the provided character.
|
||||||
|
*/
|
||||||
|
export class MaskedEditor {
|
||||||
|
/**
|
||||||
|
* @param {import('codemirror').Editor} editor CodeMirror editor instance
|
||||||
|
* @param {string} maskChar Target character being applied to all content
|
||||||
|
*/
|
||||||
|
constructor(editor, maskChar) {
|
||||||
|
this.editor = editor;
|
||||||
|
this.maskChar = maskChar;
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set and apply new masking character
|
||||||
|
*/
|
||||||
|
enable = () => {
|
||||||
|
this.enabled = true;
|
||||||
|
this.editor.setValue(this.editor.getValue());
|
||||||
|
this.editor.on('inputRead', this.maskContent);
|
||||||
|
this.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Disables masking of the editor field. */
|
||||||
|
disable = () => {
|
||||||
|
this.enabled = false;
|
||||||
|
this.editor.off('inputRead', this.maskContent);
|
||||||
|
this.editor.setValue(this.editor.getValue());
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Updates the rendered content if enabled. */
|
||||||
|
update = () => {
|
||||||
|
if (this.enabled) this.maskContent();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Replaces all rendered characters, with the provided character. */
|
||||||
|
maskContent = () => {
|
||||||
|
const content = this.editor.getValue();
|
||||||
|
this.editor.operation(() => {
|
||||||
|
// Clear previous masked text
|
||||||
|
this.editor.getAllMarks().forEach((mark) => mark.clear());
|
||||||
|
// Apply new masked text
|
||||||
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
if (content[i] !== '\n') {
|
||||||
|
const maskedNode = document.createTextNode(this.maskChar);
|
||||||
|
this.editor.markText(
|
||||||
|
{ line: this.editor.posFromIndex(i).line, ch: this.editor.posFromIndex(i).ch },
|
||||||
|
{ line: this.editor.posFromIndex(i + 1).line, ch: this.editor.posFromIndex(i + 1).ch },
|
||||||
|
{ replacedWith: maskedNode, handleMouseEvents: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPathParams) => {
|
export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPathParams) => {
|
||||||
CodeMirror.defineMode('brunovariables', function (config, parserConfig) {
|
CodeMirror.defineMode('brunovariables', function (config, parserConfig) {
|
||||||
const { pathParams = {}, ...variables } = _variables || {};
|
const { pathParams = {}, ...variables } = _variables || {};
|
||||||
|
@ -72,11 +72,10 @@ const parseCurlCommand = (curlCommand) => {
|
|||||||
parsedArguments.header.forEach((header) => {
|
parsedArguments.header.forEach((header) => {
|
||||||
if (header.indexOf('Cookie') !== -1) {
|
if (header.indexOf('Cookie') !== -1) {
|
||||||
cookieString = header;
|
cookieString = header;
|
||||||
} else {
|
}
|
||||||
const components = header.split(/:(.*)/);
|
const components = header.split(/:(.*)/);
|
||||||
if (components[1]) {
|
if (components[1]) {
|
||||||
headers[components[0]] = components[1].trim();
|
headers[components[0]] = components[1].trim();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "v1.22.0",
|
"version": "v1.24.0",
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||||
"homepage": "https://www.usebruno.com",
|
"homepage": "https://www.usebruno.com",
|
||||||
|
@ -428,17 +428,16 @@ class Watcher {
|
|||||||
this.watchers = {};
|
this.watchers = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addWatcher(win, watchPath, collectionUid, brunoConfig) {
|
addWatcher(win, watchPath, collectionUid, brunoConfig, forcePolling = false) {
|
||||||
if (this.watchers[watchPath]) {
|
if (this.watchers[watchPath]) {
|
||||||
this.watchers[watchPath].close();
|
this.watchers[watchPath].close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const ignores = brunoConfig?.ignore || [];
|
const ignores = brunoConfig?.ignore || [];
|
||||||
const self = this;
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const watcher = chokidar.watch(watchPath, {
|
const watcher = chokidar.watch(watchPath, {
|
||||||
ignoreInitial: false,
|
ignoreInitial: false,
|
||||||
usePolling: watchPath.startsWith('\\\\') ? true : false,
|
usePolling: watchPath.startsWith('\\\\') || forcePolling ? true : false,
|
||||||
ignored: (filepath) => {
|
ignored: (filepath) => {
|
||||||
const normalizedPath = filepath.replace(/\\/g, '/');
|
const normalizedPath = filepath.replace(/\\/g, '/');
|
||||||
const relativePath = path.relative(watchPath, normalizedPath);
|
const relativePath = path.relative(watchPath, normalizedPath);
|
||||||
@ -457,14 +456,35 @@ class Watcher {
|
|||||||
depth: 20
|
depth: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let startedNewWatcher = false;
|
||||||
watcher
|
watcher
|
||||||
.on('add', (pathname) => add(win, pathname, collectionUid, watchPath))
|
.on('add', (pathname) => add(win, pathname, collectionUid, watchPath))
|
||||||
.on('addDir', (pathname) => addDirectory(win, pathname, collectionUid, watchPath))
|
.on('addDir', (pathname) => addDirectory(win, pathname, collectionUid, watchPath))
|
||||||
.on('change', (pathname) => change(win, pathname, collectionUid, watchPath))
|
.on('change', (pathname) => change(win, pathname, collectionUid, watchPath))
|
||||||
.on('unlink', (pathname) => unlink(win, pathname, collectionUid, watchPath))
|
.on('unlink', (pathname) => unlink(win, pathname, collectionUid, watchPath))
|
||||||
.on('unlinkDir', (pathname) => unlinkDir(win, pathname, collectionUid, watchPath));
|
.on('unlinkDir', (pathname) => unlinkDir(win, pathname, collectionUid, watchPath))
|
||||||
|
.on('error', (error) => {
|
||||||
|
// `ENOSPC` stands for "Error No space" but is also thrown if the file watcher limit is reached.
|
||||||
|
// To prevent loops `!forcePolling` is checked.
|
||||||
|
if (error.code === 'ENOSPC' && !startedNewWatcher && !forcePolling) {
|
||||||
|
// This callback is called for every file the watcher is trying to watch. To prevent a spam of messages and
|
||||||
|
// Multiple watcher being started `startedNewWatcher` is set to prevent this.
|
||||||
|
startedNewWatcher = true;
|
||||||
|
watcher.close();
|
||||||
|
console.error(
|
||||||
|
`\nCould not start watcher for ${watchPath}:`,
|
||||||
|
'ENOSPC: System limit for number of file watchers reached!',
|
||||||
|
'Trying again with polling, this will be slower!\n',
|
||||||
|
'Update you system config to allow more concurrently watched files with:',
|
||||||
|
'"echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p"'
|
||||||
|
);
|
||||||
|
this.addWatcher(win, watchPath, collectionUid, brunoConfig, true);
|
||||||
|
} else {
|
||||||
|
console.error(`An error occurred in the watcher for: ${watchPath}`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.watchers[watchPath] = watcher;
|
this.watchers[watchPath] = watcher;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ app.on('ready', async () => {
|
|||||||
height,
|
height,
|
||||||
minWidth: 1000,
|
minWidth: 1000,
|
||||||
minHeight: 640,
|
minHeight: 640,
|
||||||
|
show: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@ -67,6 +68,9 @@ app.on('ready', async () => {
|
|||||||
mainWindow.maximize();
|
mainWindow.maximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mainWindow.once('ready-to-show', () => {
|
||||||
|
mainWindow.show();
|
||||||
|
})
|
||||||
const url = isDev
|
const url = isDev
|
||||||
? 'http://localhost:3000'
|
? 'http://localhost:3000'
|
||||||
: format({
|
: format({
|
||||||
|
@ -408,7 +408,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run post-response script
|
// run post-response script
|
||||||
const responseScript = compact(scriptingConfig.flow === 'natural' ? [
|
const responseScript = compact(scriptingConfig.flow === 'sequential' ? [
|
||||||
get(collectionRoot, 'request.script.res'), get(request, 'script.res')
|
get(collectionRoot, 'request.script.res'), get(request, 'script.res')
|
||||||
] : [
|
] : [
|
||||||
get(request, 'script.res'), get(collectionRoot, 'request.script.res')
|
get(request, 'script.res'), get(collectionRoot, 'request.script.res')
|
||||||
@ -596,7 +596,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testScript = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testScript = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
||||||
const testFile = compact(scriptingConfig.flow === 'natural' ? [
|
const testFile = compact(scriptingConfig.flow === 'sequential' ? [
|
||||||
get(collectionRoot, 'request.tests'), testScript,
|
get(collectionRoot, 'request.tests'), testScript,
|
||||||
] : [
|
] : [
|
||||||
testScript, get(collectionRoot, 'request.tests')
|
testScript, get(collectionRoot, 'request.tests')
|
||||||
@ -825,7 +825,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'renderer:run-collection-folder',
|
'renderer:run-collection-folder',
|
||||||
async (event, folder, collection, environment, runtimeVariables, recursive) => {
|
async (event, folder, collection, environment, runtimeVariables, recursive, delay) => {
|
||||||
const collectionUid = collection.uid;
|
const collectionUid = collection.uid;
|
||||||
const collectionPath = collection.pathname;
|
const collectionPath = collection.pathname;
|
||||||
const folderUid = folder ? folder.uid : null;
|
const folderUid = folder ? folder.uid : null;
|
||||||
@ -944,6 +944,18 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
timeStart = Date.now();
|
timeStart = Date.now();
|
||||||
let response, responseTime;
|
let response, responseTime;
|
||||||
try {
|
try {
|
||||||
|
if (delay && !Number.isNaN(delay) && delay > 0) {
|
||||||
|
const delayPromise = new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
|
||||||
|
const cancellationPromise = new Promise((_, reject) => {
|
||||||
|
abortController.signal.addEventListener('abort', () => {
|
||||||
|
reject(new Error('Cancelled'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.race([delayPromise, cancellationPromise]);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('axios').AxiosResponse} */
|
/** @type {import('axios').AxiosResponse} */
|
||||||
response = await axiosInstance(request);
|
response = await axiosInstance(request);
|
||||||
timeEnd = Date.now();
|
timeEnd = Date.now();
|
||||||
@ -1036,7 +1048,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testScript = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testScript = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
||||||
const testFile = compact(scriptingConfig.flow === 'natural' ? [
|
const testFile = compact(scriptingConfig.flow === 'sequential' ? [
|
||||||
get(collectionRoot, 'request.tests'), testScript
|
get(collectionRoot, 'request.tests'), testScript
|
||||||
] : [
|
] : [
|
||||||
testScript, get(collectionRoot, 'request.tests')
|
testScript, get(collectionRoot, 'request.tests')
|
||||||
|
@ -18,8 +18,8 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => {
|
|||||||
folderHeaders.set(header.name, header.value);
|
folderHeaders.set(header.name, header.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (i.uid === request.uid) {
|
||||||
let headers = get(i, 'request.headers', []);
|
const headers = i?.draft ? get(i, 'draft.request.headers', []) : get(i, 'request.headers', []);
|
||||||
headers.forEach((header) => {
|
headers.forEach((header) => {
|
||||||
if (header.enabled) {
|
if (header.enabled) {
|
||||||
folderHeaders.set(header.name, header.value);
|
folderHeaders.set(header.name, header.value);
|
||||||
@ -55,8 +55,8 @@ const mergeFolderLevelVars = (request, requestTreePath) => {
|
|||||||
folderReqVars.set(_var.name, _var.value);
|
folderReqVars.set(_var.name, _var.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (i.uid === request.uid) {
|
||||||
let vars = get(i, 'request.vars.req', []);
|
const vars = i?.draft ? get(i, 'draft.request.vars.req', []) : get(i, 'request.vars.req', []);
|
||||||
vars.forEach((_var) => {
|
vars.forEach((_var) => {
|
||||||
if (_var.enabled) {
|
if (_var.enabled) {
|
||||||
folderReqVars.set(_var.name, _var.value);
|
folderReqVars.set(_var.name, _var.value);
|
||||||
@ -91,8 +91,8 @@ const mergeFolderLevelVars = (request, requestTreePath) => {
|
|||||||
folderResVars.set(_var.name, _var.value);
|
folderResVars.set(_var.name, _var.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (i.uid === request.uid) {
|
||||||
let vars = get(i, 'request.vars.res', []);
|
const vars = i?.draft ? get(i, 'draft.request.vars.res', []) : get(i, 'request.vars.res', []);
|
||||||
vars.forEach((_var) => {
|
vars.forEach((_var) => {
|
||||||
if (_var.enabled) {
|
if (_var.enabled) {
|
||||||
folderResVars.set(_var.name, _var.value);
|
folderResVars.set(_var.name, _var.value);
|
||||||
@ -147,7 +147,7 @@ const mergeFolderLevelScripts = (request, requestTreePath, scriptFlow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (folderCombinedPostResScript.length) {
|
if (folderCombinedPostResScript.length) {
|
||||||
if (scriptFlow === 'natural') {
|
if (scriptFlow === 'sequential') {
|
||||||
request.script.res = compact([...folderCombinedPostResScript, request?.script?.res || '']).join(os.EOL);
|
request.script.res = compact([...folderCombinedPostResScript, request?.script?.res || '']).join(os.EOL);
|
||||||
} else {
|
} else {
|
||||||
request.script.res = compact([request?.script?.res || '', ...folderCombinedPostResScript.reverse()]).join(os.EOL);
|
request.script.res = compact([request?.script?.res || '', ...folderCombinedPostResScript.reverse()]).join(os.EOL);
|
||||||
@ -155,7 +155,7 @@ const mergeFolderLevelScripts = (request, requestTreePath, scriptFlow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (folderCombinedTests.length) {
|
if (folderCombinedTests.length) {
|
||||||
if (scriptFlow === 'natural') {
|
if (scriptFlow === 'sequential') {
|
||||||
request.tests = compact([...folderCombinedTests, request?.tests || '']).join(os.EOL);
|
request.tests = compact([...folderCombinedTests, request?.tests || '']).join(os.EOL);
|
||||||
} else {
|
} else {
|
||||||
request.tests = compact([request?.tests || '', ...folderCombinedTests.reverse()]).join(os.EOL);
|
request.tests = compact([request?.tests || '', ...folderCombinedTests.reverse()]).join(os.EOL);
|
||||||
@ -309,7 +309,7 @@ const prepareRequest = (item, collection) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// scriptFlow is either "sandwich" or "natural"
|
// scriptFlow is either "sandwich" or "sequential"
|
||||||
const scriptFlow = collection.brunoConfig?.scripts?.flow ?? 'sandwich';
|
const scriptFlow = collection.brunoConfig?.scripts?.flow ?? 'sandwich';
|
||||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||||
if (requestTreePath && requestTreePath.length > 0) {
|
if (requestTreePath && requestTreePath.length > 0) {
|
||||||
|
@ -13,7 +13,7 @@ const stripLastLine = (text) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getValueString = (value) => {
|
const getValueString = (value) => {
|
||||||
const hasNewLines = value.includes('\n');
|
const hasNewLines = value?.includes('\n');
|
||||||
|
|
||||||
if (!hasNewLines) {
|
if (!hasNewLines) {
|
||||||
return value;
|
return value;
|
||||||
@ -269,7 +269,6 @@ ${indentString(body.rawFile)}
|
|||||||
multipartForms
|
multipartForms
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const enabled = item.enabled ? '' : '~';
|
const enabled = item.enabled ? '' : '~';
|
||||||
|
|
||||||
if (item.type === 'text') {
|
if (item.type === 'text') {
|
||||||
return `${enabled}${item.name}: ${getValueString(item.value)}`;
|
return `${enabled}${item.name}: ${getValueString(item.value)}`;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ const varsSchema = Yup.object({
|
|||||||
|
|
||||||
const requestUrlSchema = Yup.string().min(0).defined();
|
const requestUrlSchema = Yup.string().min(0).defined();
|
||||||
const requestMethodSchema = Yup.string()
|
const requestMethodSchema = Yup.string()
|
||||||
.oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'])
|
.oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE'])
|
||||||
.required('method is required');
|
.required('method is required');
|
||||||
|
|
||||||
const graphqlBodySchema = Yup.object({
|
const graphqlBodySchema = Yup.object({
|
||||||
|
@ -32,7 +32,7 @@ describe('Request Schema Validation', () => {
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
expect(requestSchema.validate(request)).rejects.toEqual(
|
expect(requestSchema.validate(request)).rejects.toEqual(
|
||||||
validationErrorWithMessages(
|
validationErrorWithMessages(
|
||||||
'method must be one of the following values: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
|
'method must be one of the following values: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
Loading…
Reference in New Issue
Block a user