mirror of
https://github.com/usebruno/bruno.git
synced 2025-02-08 14:02:09 +01:00
Merge branch 'main' into feature/better-image-preview
This commit is contained in:
commit
623ac0bac6
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
|
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
|
||||||
|
|
||||||
# Contribution Checklist:
|
### Contribution Checklist:
|
||||||
|
|
||||||
|
- [ ] **The pull request only addresses one issue or adds one feature.**
|
||||||
- [ ] **The pull request does not introduce any breaking changes**
|
- [ ] **The pull request does not introduce any breaking changes**
|
||||||
|
- [ ] **I have added screenshots or gifs to help explain the change if applicable.**
|
||||||
- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).**
|
- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).**
|
||||||
- [ ] **Create an issue and link to the pull request.**
|
- [ ] **Create an issue and link to the pull request.**
|
||||||
|
|
||||||
|
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
|
||||||
|
22
.github/workflows/unit-tests.yml
vendored
22
.github/workflows/unit-tests.yml
vendored
@ -5,18 +5,16 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
tests:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version-file: '.nvmrc'
|
||||||
- name: Check package-lock.json
|
|
||||||
run: npm ci
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm i --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
- name: Test Package bruno-query
|
- name: Test Package bruno-query
|
||||||
run: npm run test --workspace=packages/bruno-query
|
run: npm run test --workspace=packages/bruno-query
|
||||||
- name: Build Package bruno-query
|
- name: Build Package bruno-query
|
||||||
@ -33,3 +31,15 @@ jobs:
|
|||||||
run: npm run test --workspace=packages/bruno-cli
|
run: npm run test --workspace=packages/bruno-cli
|
||||||
- name: Test Package bruno-electron
|
- name: Test Package bruno-electron
|
||||||
run: npm run test --workspace=packages/bruno-electron
|
run: npm run test --workspace=packages/bruno-electron
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci --legacy-peer-deps
|
||||||
|
- name: Run Prettier
|
||||||
|
run: npm run test:prettier:web
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
**English** | [Русский](/contributing_ru.md)
|
**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md)
|
||||||
|
|
||||||
## Lets make bruno better, together !!
|
## Lets make bruno better, together !!
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[English](/contributing.md) | **Русский**
|
[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский**
|
||||||
|
|
||||||
## Давайте вместе сделаем Бруно лучше!!!
|
## Давайте вместе сделаем Бруно лучше!!!
|
||||||
|
|
||||||
@ -34,4 +34,4 @@ Bruno построен с использованием NextJs и React. Мы т
|
|||||||
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
|
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
|
||||||
- Пример: feature/dark-mode
|
- Пример: feature/dark-mode
|
||||||
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
|
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
|
||||||
- Пример bugfix/bug-1
|
- Пример bugfix/bug-1
|
||||||
|
37
contributing_ua.md
Normal file
37
contributing_ua.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md)
|
||||||
|
|
||||||
|
## Давайте зробимо Bruno краще, разом !!
|
||||||
|
|
||||||
|
Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері.
|
||||||
|
|
||||||
|
### Стек технологій
|
||||||
|
|
||||||
|
Bruno побудований на NextJs та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron
|
||||||
|
|
||||||
|
Бібліотеки, які ми використовуємо
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Редактори коду - Codemirror
|
||||||
|
- Керування станом - Redux
|
||||||
|
- Іконки - Tabler Icons
|
||||||
|
- Форми - formik
|
||||||
|
- Валідація по схемі - Yup
|
||||||
|
- Клієнт запитів - axios
|
||||||
|
- Спостерігач за файловою системою - chokidar
|
||||||
|
|
||||||
|
### Залежності
|
||||||
|
|
||||||
|
Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
|
||||||
|
|
||||||
|
### Починаєм писати код
|
||||||
|
|
||||||
|
Будь ласка, зверніться до [development_ua.md](docs/development_ua.md) за інструкціями щодо запуску локального середовища розробки.
|
||||||
|
|
||||||
|
### Створення Pull Request-ів
|
||||||
|
|
||||||
|
- Будь ласка, робіть PR-и маленькими і сфокусованими на одній речі
|
||||||
|
- Будь ласка, слідуйте формату назв гілок
|
||||||
|
- feature/[назва feature]: Така гілка має містити зміни лише щодо конкретної feature
|
||||||
|
- Приклад: feature/dark-mode
|
||||||
|
- bugfix/[назва баґу]: Така гілка має містити лише виправлення конкретного багу
|
||||||
|
- Приклад: bugfix/bug-1
|
@ -1,4 +1,4 @@
|
|||||||
**English** | [Русский](/docs/development_ru.md)
|
**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[English](/docs/development.md) | **Русский**
|
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский**
|
||||||
|
|
||||||
## Разработка
|
## Разработка
|
||||||
|
|
||||||
|
55
docs/development_ua.md
Normal file
55
docs/development_ua.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md)
|
||||||
|
|
||||||
|
## Розробка
|
||||||
|
|
||||||
|
Bruno розробляється як декстопний застосунок. Вам потрібно запустити nextjs в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу.
|
||||||
|
|
||||||
|
### Залежності
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Локальна розробка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Використовуйте nodejs 18-ї версії
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# встановіть залежності
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# зберіть документацію graphql
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# зберіть bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# запустіть додаток next (термінал 1)
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# запустіть додаток електрон (термінал 2)
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Усунення несправностей
|
||||||
|
|
||||||
|
Ви можете зтикнутись із помилкою `Unsupported platform` коли запускаєте `npm install`. Щоб усунути цю проблему, вам потрібно видалити `node_modules` та `package-lock.json`, і тоді запустити `npm install`. Це має встановити всі потрібні для запуску додатку пекеджі.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Видаліть node_modules в піддиректоріях
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Видаліть package-lock в піддиректоріях
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестування
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -16717,7 +16717,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v0.24.0",
|
"version": "v0.25.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "^3.425.0",
|
"@aws-sdk/credential-providers": "^3.425.0",
|
||||||
"@usebruno/js": "0.8.0",
|
"@usebruno/js": "0.8.0",
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"build:electron:snap": "./scripts/build-electron.sh snap",
|
"build:electron:snap": "./scripts/build-electron.sh snap",
|
||||||
"test:e2e": "npx playwright test",
|
"test:e2e": "npx playwright test",
|
||||||
"test:report": "npx playwright show-report",
|
"test:report": "npx playwright show-report",
|
||||||
|
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
||||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
|
import Tooltip from 'components/Tooltip';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||||
const proxySchema = Yup.object({
|
const proxySchema = Yup.object({
|
||||||
enabled: Yup.string().oneOf(['global', 'enabled', 'disabled']),
|
use: Yup.string().oneOf(['global', 'true', 'false']),
|
||||||
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
|
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||||
hostname: Yup.string()
|
hostname: Yup.string()
|
||||||
.when('enabled', {
|
.when('use', {
|
||||||
is: 'enabled',
|
is: true,
|
||||||
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
|
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
|
||||||
otherwise: (hostname) => hostname.nullable()
|
otherwise: (hostname) => hostname.nullable()
|
||||||
})
|
})
|
||||||
.max(1024),
|
.max(1024),
|
||||||
port: Yup.number()
|
port: Yup.number()
|
||||||
.when('enabled', {
|
.when('use', {
|
||||||
is: 'enabled',
|
is: true,
|
||||||
then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'),
|
then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'),
|
||||||
otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null))
|
otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null))
|
||||||
})
|
})
|
||||||
@ -26,11 +26,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
.max(65535),
|
.max(65535),
|
||||||
auth: Yup.object()
|
auth: Yup.object()
|
||||||
.when('enabled', {
|
.when('enabled', {
|
||||||
is: 'enabled',
|
is: true,
|
||||||
then: Yup.object({
|
then: Yup.object({
|
||||||
enabled: Yup.boolean(),
|
enabled: Yup.boolean(),
|
||||||
username: Yup.string()
|
username: Yup.string()
|
||||||
.when(['enabled'], {
|
.when('enabled', {
|
||||||
is: true,
|
is: true,
|
||||||
then: (username) => username.required('Specify username for proxy authentication.')
|
then: (username) => username.required('Specify username for proxy authentication.')
|
||||||
})
|
})
|
||||||
@ -44,12 +44,12 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
noProxy: Yup.string().optional().max(1024)
|
bypassProxy: Yup.string().optional().max(1024)
|
||||||
});
|
});
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
enabled: proxyConfig.enabled || 'global',
|
use: proxyConfig.use || 'global',
|
||||||
protocol: proxyConfig.protocol || 'http',
|
protocol: proxyConfig.protocol || 'http',
|
||||||
hostname: proxyConfig.hostname || '',
|
hostname: proxyConfig.hostname || '',
|
||||||
port: proxyConfig.port || '',
|
port: proxyConfig.port || '',
|
||||||
@ -58,13 +58,20 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
||||||
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
||||||
},
|
},
|
||||||
noProxy: proxyConfig.noProxy || ''
|
bypassProxy: proxyConfig.bypassProxy || ''
|
||||||
},
|
},
|
||||||
validationSchema: proxySchema,
|
validationSchema: proxySchema,
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
proxySchema
|
proxySchema
|
||||||
.validate(values, { abortEarly: true })
|
.validate(values, { abortEarly: true })
|
||||||
.then((validatedProxy) => {
|
.then((validatedProxy) => {
|
||||||
|
// serialize 'use' to boolean
|
||||||
|
if (validatedProxy.use === 'true') {
|
||||||
|
validatedProxy.use = true;
|
||||||
|
} else if (validatedProxy.use === 'false') {
|
||||||
|
validatedProxy.use = false;
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate(validatedProxy);
|
onUpdate(validatedProxy);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -76,7 +83,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
formik.setValues({
|
formik.setValues({
|
||||||
enabled: proxyConfig.enabled || 'global',
|
use: proxyConfig.use === true ? 'true' : proxyConfig.use === false ? 'false' : 'global',
|
||||||
protocol: proxyConfig.protocol || 'http',
|
protocol: proxyConfig.protocol || 'http',
|
||||||
hostname: proxyConfig.hostname || '',
|
hostname: proxyConfig.hostname || '',
|
||||||
port: proxyConfig.port || '',
|
port: proxyConfig.port || '',
|
||||||
@ -85,32 +92,37 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
||||||
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
||||||
},
|
},
|
||||||
noProxy: proxyConfig.noProxy || ''
|
bypassProxy: proxyConfig.bypassProxy || ''
|
||||||
});
|
});
|
||||||
}, [proxyConfig]);
|
}, [proxyConfig]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
||||||
<label className="settings-label">
|
|
||||||
<ul className="mb-3">
|
|
||||||
<li>global - use global config</li>
|
|
||||||
<li>enabled - use collection config</li>
|
|
||||||
<li>disable - disable proxy</li>
|
|
||||||
</ul>
|
|
||||||
</label>
|
|
||||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||||
<div className="mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="enabled">
|
<label className="settings-label flex items-center" htmlFor="enabled">
|
||||||
Config
|
Config
|
||||||
|
<Tooltip
|
||||||
|
text={`
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li><span style="width: 50px;display:inline-block;">global</span> - use global proxy config</li>
|
||||||
|
<li><span style="width: 50px;display:inline-block;">enabled</span> - use collection proxy config</li>
|
||||||
|
<li><span style="width: 50px;display:inline-block;">disable</span> - disable proxy</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
tooltipId="request-var"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className="flex items-center">
|
<label className="flex items-center">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="enabled"
|
name="use"
|
||||||
value="global"
|
value="global"
|
||||||
checked={formik.values.enabled === 'global'}
|
checked={formik.values.use === 'global'}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
/>
|
/>
|
||||||
@ -119,9 +131,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<label className="flex items-center ml-4">
|
<label className="flex items-center ml-4">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="enabled"
|
name="use"
|
||||||
value="enabled"
|
value={'true'}
|
||||||
checked={formik.values.enabled === 'enabled'}
|
checked={formik.values.use === 'true'}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
/>
|
/>
|
||||||
@ -130,9 +142,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<label className="flex items-center ml-4">
|
<label className="flex items-center ml-4">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="enabled"
|
name="use"
|
||||||
value="disabled"
|
value={'false'}
|
||||||
checked={formik.values.enabled === 'disabled'}
|
checked={formik.values.use === 'false'}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
className="mr-1"
|
className="mr-1"
|
||||||
/>
|
/>
|
||||||
@ -285,23 +297,23 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="noProxy">
|
<label className="settings-label" htmlFor="bypassProxy">
|
||||||
Proxy Bypass
|
Proxy Bypass
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="noProxy"
|
id="bypassProxy"
|
||||||
type="text"
|
type="text"
|
||||||
name="noProxy"
|
name="bypassProxy"
|
||||||
className="block textbox"
|
className="block textbox"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.noProxy || ''}
|
value={formik.values.bypassProxy || ''}
|
||||||
/>
|
/>
|
||||||
{formik.touched.noProxy && formik.errors.noProxy ? (
|
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||||
<div className="ml-3 text-red-500">{formik.errors.noProxy}</div>
|
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
@ -1,67 +1,103 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { savePreferences } from 'providers/ReduxStore/slices/app';
|
import { savePreferences } from 'providers/ReduxStore/slices/app';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
const General = ({ close }) => {
|
const General = ({ close }) => {
|
||||||
const preferences = useSelector((state) => state.app.preferences);
|
const preferences = useSelector((state) => state.app.preferences);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
|
const preferencesSchema = Yup.object().shape({
|
||||||
const [timeout, setTimeout] = useState(preferences.request.timeout);
|
sslVerification: Yup.boolean(),
|
||||||
|
timeout: Yup.mixed()
|
||||||
|
.transform((value, originalValue) => {
|
||||||
|
return originalValue === '' ? undefined : value;
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.test('isNumber', 'Request Timeout must be a number', (value) => {
|
||||||
|
return value === undefined || !isNaN(value);
|
||||||
|
})
|
||||||
|
.test('isValidTimeout', 'Request Timeout must be equal or greater than 0', (value) => {
|
||||||
|
return value === undefined || Number(value) >= 0;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const handleSave = () => {
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
sslVerification: preferences.request.sslVerification,
|
||||||
|
timeout: preferences.request.timeout
|
||||||
|
},
|
||||||
|
validationSchema: preferencesSchema,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
try {
|
||||||
|
const newPreferences = await preferencesSchema.validate(values, { abortEarly: true });
|
||||||
|
handleSave(newPreferences);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Preferences validation error:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = (newPreferences) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
savePreferences({
|
savePreferences({
|
||||||
...preferences,
|
...preferences,
|
||||||
request: {
|
request: {
|
||||||
sslVerification,
|
sslVerification: newPreferences.sslVerification,
|
||||||
timeout
|
timeout: newPreferences.timeout
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
).then(() => {
|
)
|
||||||
close();
|
.then(() => {
|
||||||
});
|
close();
|
||||||
};
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('Failed to update preferences'));
|
||||||
const handleTimeoutChange = (value) => {
|
|
||||||
const validTimeout = isNaN(Number(value)) ? timeout : Number(value);
|
|
||||||
setTimeout(validTimeout);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="flex items-center mt-2">
|
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||||
<label className="mr-2 select-none" style={{ minWidth: 200 }} htmlFor="ssl-cert-verification">
|
<div className="flex items-center mt-2">
|
||||||
TLS Certificate Verification
|
<label className="block font-medium mr-2 select-none" style={{ minWidth: 200 }} htmlFor="sslVerification">
|
||||||
</label>
|
SSL/TLS Certificate Verification
|
||||||
<input
|
</label>
|
||||||
id="ssl-cert-verification"
|
<input
|
||||||
type="checkbox"
|
id="ssl-cert-verification"
|
||||||
checked={sslVerification}
|
type="checkbox"
|
||||||
onChange={() => setSslVerification(!sslVerification)}
|
name="sslVerification"
|
||||||
className="mousetrap mr-0"
|
checked={formik.values.sslVerification}
|
||||||
/>
|
onChange={formik.handleChange}
|
||||||
</div>
|
className="mousetrap mr-0"
|
||||||
<div className="flex flex-col mt-6">
|
/>
|
||||||
<label className="block font-medium select-none">Request Timeout (in ms)</label>
|
</div>
|
||||||
<input
|
<div className="flex flex-col mt-6">
|
||||||
type="text"
|
<label className="block font-medium select-none" htmlFor="timeout">
|
||||||
className="block textbox mt-2 w-1/4"
|
Request Timeout (in ms)
|
||||||
autoComplete="off"
|
</label>
|
||||||
autoCorrect="off"
|
<input
|
||||||
autoCapitalize="off"
|
type="text"
|
||||||
spellCheck="false"
|
name="timeout"
|
||||||
onChange={(e) => handleTimeoutChange(e.target.value)}
|
className="block textbox mt-2 w-16"
|
||||||
defaultValue={timeout === 0 ? '' : timeout}
|
autoComplete="off"
|
||||||
/>
|
autoCorrect="off"
|
||||||
</div>
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
<div className="mt-10">
|
onChange={formik.handleChange}
|
||||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
value={formik.values.timeout}
|
||||||
Save
|
/>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
{formik.touched.timeout && formik.errors.timeout ? (
|
||||||
|
<div className="text-red-500">{formik.errors.timeout}</div>
|
||||||
|
) : null}
|
||||||
|
<div className="mt-10">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
noProxy: Yup.string().optional().max(1024)
|
bypassProxy: Yup.string().optional().max(1024)
|
||||||
});
|
});
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
@ -63,7 +63,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
|
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
|
||||||
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
|
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
|
||||||
},
|
},
|
||||||
noProxy: preferences.proxy.noProxy || ''
|
bypassProxy: preferences.proxy.bypassProxy || ''
|
||||||
},
|
},
|
||||||
validationSchema: proxySchema,
|
validationSchema: proxySchema,
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
@ -101,21 +101,21 @@ const ProxySettings = ({ close }) => {
|
|||||||
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
|
username: preferences.proxy.auth ? preferences.proxy.auth.username || '' : '',
|
||||||
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
|
password: preferences.proxy.auth ? preferences.proxy.auth.password || '' : ''
|
||||||
},
|
},
|
||||||
noProxy: preferences.proxy.noProxy || ''
|
bypassProxy: preferences.proxy.bypassProxy || ''
|
||||||
});
|
});
|
||||||
}, [preferences]);
|
}, [preferences]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
|
||||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="enabled">
|
<label className="settings-label" htmlFor="enabled">
|
||||||
Enabled
|
Enabled
|
||||||
</label>
|
</label>
|
||||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="protocol">
|
<label className="settings-label" htmlFor="protocol">
|
||||||
Protocol
|
Protocol
|
||||||
</label>
|
</label>
|
||||||
@ -166,7 +166,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="hostname">
|
<label className="settings-label" htmlFor="hostname">
|
||||||
Hostname
|
Hostname
|
||||||
</label>
|
</label>
|
||||||
@ -186,7 +186,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="port">
|
<label className="settings-label" htmlFor="port">
|
||||||
Port
|
Port
|
||||||
</label>
|
</label>
|
||||||
@ -206,7 +206,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.enabled">
|
<label className="settings-label" htmlFor="auth.enabled">
|
||||||
Auth
|
Auth
|
||||||
</label>
|
</label>
|
||||||
@ -218,7 +218,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.username">
|
<label className="settings-label" htmlFor="auth.username">
|
||||||
Username
|
Username
|
||||||
</label>
|
</label>
|
||||||
@ -238,7 +238,7 @@ const ProxySettings = ({ close }) => {
|
|||||||
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.password">
|
<label className="settings-label" htmlFor="auth.password">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
@ -259,24 +259,24 @@ const ProxySettings = ({ close }) => {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="noProxy">
|
<label className="settings-label" htmlFor="bypassProxy">
|
||||||
Proxy Bypass
|
Proxy Bypass
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="noProxy"
|
id="bypassProxy"
|
||||||
type="text"
|
type="text"
|
||||||
name="noProxy"
|
name="bypassProxy"
|
||||||
className="block textbox"
|
className="block textbox"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
value={formik.values.noProxy || ''}
|
value={formik.values.bypassProxy || ''}
|
||||||
/>
|
/>
|
||||||
{formik.touched.noProxy && formik.errors.noProxy ? (
|
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||||
<div className="ml-3 text-red-500">{formik.errors.noProxy}</div>
|
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
@ -36,12 +36,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
|
|
||||||
const request = item.draft ? item.draft.request : item.request;
|
const request = item.draft ? item.draft.request : item.request;
|
||||||
|
|
||||||
let {
|
let { schema, loadSchema, isLoading: isSchemaLoading } = useGraphqlSchema(url, environment, request, collection);
|
||||||
schema,
|
|
||||||
loadSchema,
|
|
||||||
isLoading: isSchemaLoading,
|
|
||||||
error: schemaError
|
|
||||||
} = useGraphqlSchema(url, environment, request, collection);
|
|
||||||
|
|
||||||
const loadGqlSchema = () => {
|
const loadGqlSchema = () => {
|
||||||
if (!isSchemaLoading) {
|
if (!isSchemaLoading) {
|
||||||
|
@ -26,7 +26,12 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
|||||||
const loadSchema = () => {
|
const loadSchema = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetchGqlSchema(endpoint, environment, request, collection)
|
fetchGqlSchema(endpoint, environment, request, collection)
|
||||||
.then((res) => res.data)
|
.then((res) => {
|
||||||
|
if (!res || res.status !== 200) {
|
||||||
|
return Promise.reject(new Error(res.statusText));
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
})
|
||||||
.then((s) => {
|
.then((s) => {
|
||||||
if (s && s.data) {
|
if (s && s.data) {
|
||||||
setSchema(buildClientSchema(s.data));
|
setSchema(buildClientSchema(s.data));
|
||||||
@ -40,7 +45,7 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setError(err);
|
setError(err);
|
||||||
toast.error('Error occurred while loading GraphQL Schema');
|
toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ const ResponseSize = ({ size }) => {
|
|||||||
sizeToDisplay = size + 'B';
|
sizeToDisplay = size + 'B';
|
||||||
}
|
}
|
||||||
|
|
||||||
return <StyledWrapper className="ml-4">{sizeToDisplay}</StyledWrapper>;
|
return (
|
||||||
|
<StyledWrapper title={size.toLocaleString() + 'B'} className="ml-4">
|
||||||
|
{sizeToDisplay}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default ResponseSize;
|
export default ResponseSize;
|
||||||
|
@ -88,44 +88,41 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
switch (event.button) {
|
if (isItemARequest(item)) {
|
||||||
case 0: // left click
|
dispatch(hideHomePage());
|
||||||
if (isItemARequest(item)) {
|
if (itemIsOpenedInTabs(item, tabs)) {
|
||||||
dispatch(hideHomePage());
|
|
||||||
if (itemIsOpenedInTabs(item, tabs)) {
|
|
||||||
dispatch(
|
|
||||||
focusTab({
|
|
||||||
uid: item.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
|
||||||
addTab({
|
|
||||||
uid: item.uid,
|
|
||||||
collectionUid: collection.uid,
|
|
||||||
requestPaneTab: getDefaultRequestPaneTab(item)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(
|
dispatch(
|
||||||
collectionFolderClicked({
|
focusTab({
|
||||||
itemUid: item.uid,
|
uid: item.uid
|
||||||
collectionUid: collection.uid
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
case 2: // right click
|
}
|
||||||
const _menuDropdown = dropdownTippyRef.current;
|
dispatch(
|
||||||
if (_menuDropdown) {
|
addTab({
|
||||||
let menuDropdownBehavior = 'show';
|
uid: item.uid,
|
||||||
if (_menuDropdown.state.isShown) {
|
collectionUid: collection.uid,
|
||||||
menuDropdownBehavior = 'hide';
|
requestPaneTab: getDefaultRequestPaneTab(item)
|
||||||
}
|
})
|
||||||
_menuDropdown[menuDropdownBehavior]();
|
);
|
||||||
}
|
return;
|
||||||
return;
|
}
|
||||||
|
dispatch(
|
||||||
|
collectionFolderClicked({
|
||||||
|
itemUid: item.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRightClick = (event) => {
|
||||||
|
const _menuDropdown = dropdownTippyRef.current;
|
||||||
|
if (_menuDropdown) {
|
||||||
|
let menuDropdownBehavior = 'show';
|
||||||
|
if (_menuDropdown.state.isShown) {
|
||||||
|
menuDropdownBehavior = 'hide';
|
||||||
|
}
|
||||||
|
_menuDropdown[menuDropdownBehavior]();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,7 +200,8 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
? indents.map((i) => {
|
? indents.map((i) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onMouseUp={handleClick}
|
onClick={handleClick}
|
||||||
|
onContextMenu={handleRightClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
className="indent-block"
|
className="indent-block"
|
||||||
key={i}
|
key={i}
|
||||||
@ -219,7 +217,8 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
<div
|
<div
|
||||||
onMouseUp={handleClick}
|
onClick={handleClick}
|
||||||
|
onContextMenu={handleRightClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
className="flex flex-grow items-center h-full overflow-hidden"
|
className="flex flex-grow items-center h-full overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
|
@ -66,20 +66,17 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
|
dispatch(collectionClicked(collection.uid));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRightClick = (event) => {
|
||||||
const _menuDropdown = menuDropdownTippyRef.current;
|
const _menuDropdown = menuDropdownTippyRef.current;
|
||||||
switch (event.button) {
|
if (_menuDropdown) {
|
||||||
case 0: // left click
|
let menuDropdownBehavior = 'show';
|
||||||
dispatch(collectionClicked(collection.uid));
|
if (_menuDropdown.state.isShown) {
|
||||||
return;
|
menuDropdownBehavior = 'hide';
|
||||||
case 2: // right click
|
}
|
||||||
if (_menuDropdown) {
|
_menuDropdown[menuDropdownBehavior]();
|
||||||
let menuDropdownBehavior = 'show';
|
|
||||||
if (_menuDropdown.state.isShown) {
|
|
||||||
menuDropdownBehavior = 'hide';
|
|
||||||
}
|
|
||||||
_menuDropdown[menuDropdownBehavior]();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,7 +135,11 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
||||||
)}
|
)}
|
||||||
<div className="flex py-1 collection-name items-center" ref={drop}>
|
<div className="flex py-1 collection-name items-center" ref={drop}>
|
||||||
<div className="flex flex-grow items-center overflow-hidden" onMouseUp={handleClick}>
|
<div
|
||||||
|
className="flex flex-grow items-center overflow-hidden"
|
||||||
|
onClick={handleClick}
|
||||||
|
onContextMenu={handleRightClick}
|
||||||
|
>
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size={16}
|
size={16}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
|
@ -76,7 +76,12 @@ const CreateCollection = ({ onClose }) => {
|
|||||||
name="collectionName"
|
name="collectionName"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="block textbox mt-2 w-full"
|
className="block textbox mt-2 w-full"
|
||||||
onChange={formik.handleChange}
|
onChange={(e) => {
|
||||||
|
formik.handleChange(e);
|
||||||
|
if (formik.values.collectionName === formik.values.collectionFolderName) {
|
||||||
|
formik.setFieldValue('collectionFolderName', e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
|
@ -18,7 +18,7 @@ const Tooltip = ({ text, tooltipId }) => {
|
|||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||||
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
|
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
|
||||||
</svg>
|
</svg>
|
||||||
<ReactTooltip anchorId={tooltipId} content={text} />
|
<ReactTooltip anchorId={tooltipId} html={text} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,8 @@ const initialState = {
|
|||||||
showHomePage: false,
|
showHomePage: false,
|
||||||
preferences: {
|
preferences: {
|
||||||
request: {
|
request: {
|
||||||
sslVerification: true
|
sslVerification: true,
|
||||||
|
timeout: 0
|
||||||
},
|
},
|
||||||
font: {
|
font: {
|
||||||
codeFont: 'default'
|
codeFont: 'default'
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema';
|
import { collectionSchema, itemSchema, environmentSchema, environmentsSchema } from '@usebruno/schema';
|
||||||
import { waitForNextTick } from 'utils/common';
|
import { waitForNextTick } from 'utils/common';
|
||||||
import { getDirectoryName, isWindowsOS } from 'utils/common/platform';
|
import { getDirectoryName, isWindowsOS, PATH_SEPARATOR } from 'utils/common/platform';
|
||||||
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -31,7 +31,6 @@ import {
|
|||||||
requestCancelled,
|
requestCancelled,
|
||||||
responseReceived,
|
responseReceived,
|
||||||
newItem as _newItem,
|
newItem as _newItem,
|
||||||
renameItem as _renameItem,
|
|
||||||
cloneItem as _cloneItem,
|
cloneItem as _cloneItem,
|
||||||
deleteItem as _deleteItem,
|
deleteItem as _deleteItem,
|
||||||
saveRequest as _saveRequest,
|
saveRequest as _saveRequest,
|
||||||
@ -48,8 +47,6 @@ import { resolveRequestFilename } from 'utils/common/platform';
|
|||||||
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
import { each } from 'lodash';
|
import { each } from 'lodash';
|
||||||
|
|
||||||
const PATH_SEPARATOR = path.sep;
|
|
||||||
|
|
||||||
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
@ -186,11 +183,6 @@ export const cancelRequest = (cancelTokenUid, item, collection) => (dispatch) =>
|
|||||||
.catch((err) => console.log(err));
|
.catch((err) => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo: this can be directly put inside the collections/index.js file
|
|
||||||
// the coding convention is to put only actions that need ipc in this file
|
|
||||||
export const sortCollections = (order) => (dispatch) => {
|
|
||||||
dispatch(_sortCollections(order));
|
|
||||||
};
|
|
||||||
export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => {
|
export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
@ -308,19 +300,7 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta
|
|||||||
}
|
}
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(resolve).catch(reject);
|
||||||
.invoke('renderer:rename-item', item.pathname, newPathname, newName)
|
|
||||||
.then(() => {
|
|
||||||
// In case of Mac and Linux, we get the unlinkDir and addDir IPC events from electron which takes care of updating the state
|
|
||||||
// But in windows we don't get those events, so we need to update the state manually
|
|
||||||
// This looks like an issue in our watcher library chokidar
|
|
||||||
// GH: https://github.com/usebruno/bruno/issues/251
|
|
||||||
if (isWindowsOS()) {
|
|
||||||
dispatch(_renameItem({ newName, itemUid, collectionUid }));
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -405,13 +385,6 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => {
|
|||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:delete-item', item.pathname, item.type)
|
.invoke('renderer:delete-item', item.pathname, item.type)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// In case of Mac and Linux, we get the unlinkDir IPC event from electron which takes care of updating the state
|
|
||||||
// But in windows we don't get those events, so we need to update the state manually
|
|
||||||
// This looks like an issue in our watcher library chokidar
|
|
||||||
// GH: https://github.com/usebruno/bruno/issues/265
|
|
||||||
if (isWindowsOS()) {
|
|
||||||
dispatch(_deleteItem({ itemUid, collectionUid }));
|
|
||||||
}
|
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((error) => reject(error));
|
.catch((error) => reject(error));
|
||||||
@ -420,6 +393,9 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sortCollections = () => (dispatch) => {
|
||||||
|
dispatch(_sortCollections());
|
||||||
|
};
|
||||||
export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => {
|
export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import path from 'path';
|
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import map from 'lodash/map';
|
import map from 'lodash/map';
|
||||||
@ -25,9 +24,7 @@ import {
|
|||||||
areItemsTheSameExceptSeqUpdate
|
areItemsTheSameExceptSeqUpdate
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { parseQueryParams, stringifyQueryParams } from 'utils/url';
|
import { parseQueryParams, stringifyQueryParams } from 'utils/url';
|
||||||
import { getSubdirectoriesFromRoot, getDirectoryName } from 'utils/common/platform';
|
import { getSubdirectoriesFromRoot, getDirectoryName, PATH_SEPARATOR } from 'utils/common/platform';
|
||||||
|
|
||||||
const PATH_SEPARATOR = path.sep;
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
collections: [],
|
collections: [],
|
||||||
|
@ -48,3 +48,5 @@ export const isMacOS = () => {
|
|||||||
|
|
||||||
return osFamily.includes('os x');
|
return osFamily.includes('os x');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PATH_SEPARATOR = isWindowsOS() ? '\\' : '/';
|
||||||
|
@ -129,7 +129,7 @@ const runSingleRequest = async function (
|
|||||||
|
|
||||||
// set proxy if enabled
|
// set proxy if enabled
|
||||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||||
const shouldProxy = shouldUseProxy(request.url, get(brunoConfig, 'proxy.noProxy', ''));
|
const shouldProxy = shouldUseProxy(request.url, get(brunoConfig, 'proxy.bypassProxy', ''));
|
||||||
if (proxyEnabled && shouldProxy) {
|
if (proxyEnabled && shouldProxy) {
|
||||||
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
||||||
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
||||||
|
@ -11,13 +11,14 @@ const DEFAULT_PORTS = {
|
|||||||
/**
|
/**
|
||||||
* check for proxy bypass, Copied form 'proxy-from-env'
|
* check for proxy bypass, Copied form 'proxy-from-env'
|
||||||
*/
|
*/
|
||||||
const shouldUseProxy = (url, proxyByPass) => {
|
const shouldUseProxy = (url, proxyBypass) => {
|
||||||
if (proxyByPass === '*') {
|
if (proxyBypass === '*') {
|
||||||
return false; // Never proxy if wildcard is set.
|
return false; // Never proxy if wildcard is set.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!proxyByPass) {
|
// use proxy if no proxyBypass is set
|
||||||
return true; // use proxy if enabled
|
if (!proxyBypass || typeof proxyBypass !== 'string' || isEmpty(proxyBypass.trim())) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
|
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
|
||||||
@ -34,7 +35,7 @@ const shouldUseProxy = (url, proxyByPass) => {
|
|||||||
hostname = hostname.replace(/:\d*$/, '');
|
hostname = hostname.replace(/:\d*$/, '');
|
||||||
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
|
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
|
||||||
|
|
||||||
return proxyByPass.split(/[,;\s]/).every(function (dontProxyFor) {
|
return proxyBypass.split(/[,;\s]/).every(function (dontProxyFor) {
|
||||||
if (!dontProxyFor) {
|
if (!dontProxyFor) {
|
||||||
return true; // Skip zero-length hosts.
|
return true; // Skip zero-length hosts.
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ const template = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'window',
|
role: 'window',
|
||||||
submenu: [{ role: 'minimize' }, { role: 'close' }]
|
submenu: [{ role: 'minimize' }, { role: 'close', accelerator: 'CommandOrControl+Shift+Q' }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'help',
|
role: 'help',
|
||||||
|
@ -10,7 +10,7 @@ const registerNetworkIpc = require('./ipc/network');
|
|||||||
const registerCollectionsIpc = require('./ipc/collection');
|
const registerCollectionsIpc = require('./ipc/collection');
|
||||||
const registerPreferencesIpc = require('./ipc/preferences');
|
const registerPreferencesIpc = require('./ipc/preferences');
|
||||||
const Watcher = require('./app/watcher');
|
const Watcher = require('./app/watcher');
|
||||||
const { loadWindowState, saveWindowState } = require('./utils/window');
|
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
|
||||||
|
|
||||||
const lastOpenedCollections = new LastOpenedCollections();
|
const lastOpenedCollections = new LastOpenedCollections();
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ let watcher;
|
|||||||
|
|
||||||
// Prepare the renderer once the app is ready
|
// Prepare the renderer once the app is ready
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
const { x, y, width, height } = loadWindowState();
|
const { maximized, x, y, width, height } = loadWindowState();
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
x,
|
x,
|
||||||
@ -55,6 +55,10 @@ app.on('ready', async () => {
|
|||||||
// autoHideMenuBar: true
|
// autoHideMenuBar: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (maximized) {
|
||||||
|
mainWindow.maximize();
|
||||||
|
}
|
||||||
|
|
||||||
const url = isDev
|
const url = isDev
|
||||||
? 'http://localhost:3000'
|
? 'http://localhost:3000'
|
||||||
: format({
|
: format({
|
||||||
@ -66,8 +70,17 @@ app.on('ready', async () => {
|
|||||||
mainWindow.loadURL(url);
|
mainWindow.loadURL(url);
|
||||||
watcher = new Watcher();
|
watcher = new Watcher();
|
||||||
|
|
||||||
mainWindow.on('resize', () => saveWindowState(mainWindow));
|
const handleBoundsChange = () => {
|
||||||
mainWindow.on('move', () => saveWindowState(mainWindow));
|
if (!mainWindow.isMaximized()) {
|
||||||
|
saveBounds(mainWindow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mainWindow.on('resize', handleBoundsChange);
|
||||||
|
mainWindow.on('move', handleBoundsChange);
|
||||||
|
|
||||||
|
mainWindow.on('maximize', () => saveMaximized(true));
|
||||||
|
mainWindow.on('unmaximize', () => saveMaximized(false));
|
||||||
|
|
||||||
mainWindow.webContents.on('new-window', function (e, url) {
|
mainWindow.webContents.on('new-window', function (e, url) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -18,7 +18,6 @@ const { stringifyJson } = require('../utils/common');
|
|||||||
const { openCollectionDialog } = require('../app/collections');
|
const { openCollectionDialog } = require('../app/collections');
|
||||||
const { generateUidBasedOnHash } = require('../utils/common');
|
const { generateUidBasedOnHash } = require('../utils/common');
|
||||||
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
||||||
const { setPreferences } = require('../store/preferences');
|
|
||||||
const EnvironmentSecretsStore = require('../store/env-secrets');
|
const EnvironmentSecretsStore = require('../store/env-secrets');
|
||||||
|
|
||||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||||
|
@ -5,7 +5,7 @@ function isStrPresent(str) {
|
|||||||
return str && str !== '' && str !== 'undefined';
|
return str && str !== '' && str !== 'undefined';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveCredentials(request) {
|
async function resolveAwsV4Credentials(request) {
|
||||||
const awsv4 = request.awsv4config;
|
const awsv4 = request.awsv4config;
|
||||||
if (isStrPresent(awsv4.profileName)) {
|
if (isStrPresent(awsv4.profileName)) {
|
||||||
try {
|
try {
|
||||||
@ -52,5 +52,5 @@ function addAwsV4Interceptor(axiosInstance, request) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
addAwsV4Interceptor,
|
addAwsV4Interceptor,
|
||||||
resolveCredentials
|
resolveAwsV4Credentials
|
||||||
};
|
};
|
||||||
|
@ -16,14 +16,14 @@ const { uuid } = require('../../utils/common');
|
|||||||
const interpolateVars = require('./interpolate-vars');
|
const interpolateVars = require('./interpolate-vars');
|
||||||
const { interpolateString } = require('./interpolate-string');
|
const { interpolateString } = require('./interpolate-string');
|
||||||
const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper');
|
const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper');
|
||||||
const { preferences } = require('../../store/preferences');
|
const { preferencesUtil } = require('../../store/preferences');
|
||||||
const { getProcessEnvVars } = require('../../store/process-env');
|
const { getProcessEnvVars } = require('../../store/process-env');
|
||||||
const { getBrunoConfig } = require('../../store/bruno-config');
|
const { getBrunoConfig } = require('../../store/bruno-config');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||||
const { makeAxiosInstance } = require('./axios-instance');
|
const { makeAxiosInstance } = require('./axios-instance');
|
||||||
const { addAwsV4Interceptor, resolveCredentials } = require('./awsv4auth-helper');
|
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||||
const { shouldUseProxy } = require('../../utils/proxy-util');
|
const { shouldUseProxy } = require('../../utils/proxy-util');
|
||||||
|
|
||||||
// override the default escape function to prevent escaping
|
// override the default escape function to prevent escaping
|
||||||
@ -86,7 +86,7 @@ const getSize = (data) => {
|
|||||||
|
|
||||||
const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => {
|
const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => {
|
||||||
const httpsAgentRequestFields = {};
|
const httpsAgentRequestFields = {};
|
||||||
if (!preferences.isTlsVerification()) {
|
if (!preferencesUtil.shouldVerifyTls()) {
|
||||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +123,11 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
|
|||||||
let proxyConfig = get(brunoConfig, 'proxy', {});
|
let proxyConfig = get(brunoConfig, 'proxy', {});
|
||||||
let proxyEnabled = get(proxyConfig, 'enabled', 'disabled');
|
let proxyEnabled = get(proxyConfig, 'enabled', 'disabled');
|
||||||
if (proxyEnabled === 'global') {
|
if (proxyEnabled === 'global') {
|
||||||
proxyConfig = preferences.getProxyConfig();
|
proxyConfig = preferencesUtil.getGlobalProxyConfig();
|
||||||
proxyEnabled = get(proxyConfig, 'enabled', false);
|
proxyEnabled = get(proxyConfig, 'enabled', false);
|
||||||
}
|
}
|
||||||
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'noProxy', ''));
|
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
|
||||||
if ((proxyEnabled === true || proxyEnabled === 'enabled') && shouldProxy) {
|
if (proxyEnabled === true && shouldProxy) {
|
||||||
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
|
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
|
||||||
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
|
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
|
||||||
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
|
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
|
||||||
@ -164,12 +164,12 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
|
|||||||
const axiosInstance = makeAxiosInstance();
|
const axiosInstance = makeAxiosInstance();
|
||||||
|
|
||||||
if (request.awsv4config) {
|
if (request.awsv4config) {
|
||||||
request.awsv4config = await resolveCredentials(request);
|
request.awsv4config = await resolveAwsV4Credentials(request);
|
||||||
addAwsV4Interceptor(axiosInstance, request);
|
addAwsV4Interceptor(axiosInstance, request);
|
||||||
delete request.awsv4config;
|
delete request.awsv4config;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.timeout = preferences.getTimeout();
|
request.timeout = preferencesUtil.getRequestTimeout();
|
||||||
|
|
||||||
return axiosInstance;
|
return axiosInstance;
|
||||||
};
|
};
|
||||||
@ -318,8 +318,37 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
processEnvVars
|
processEnvVars
|
||||||
);
|
);
|
||||||
|
|
||||||
/** @type {import('axios').AxiosResponse} */
|
let response, responseTime;
|
||||||
const response = await axiosInstance(request);
|
try {
|
||||||
|
/** @type {import('axios').AxiosResponse} */
|
||||||
|
response = await axiosInstance(request);
|
||||||
|
|
||||||
|
// Prevents the duration on leaking to the actual result
|
||||||
|
responseTime = response.headers.get('request-duration');
|
||||||
|
response.headers.delete('request-duration');
|
||||||
|
} catch (error) {
|
||||||
|
deleteCancelToken(cancelTokenUid);
|
||||||
|
|
||||||
|
// if it's a cancel request, don't continue
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
let error = new Error('Request cancelled');
|
||||||
|
error.isCancel = true;
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error?.response) {
|
||||||
|
response = error.response;
|
||||||
|
|
||||||
|
// Prevents the duration on leaking to the actual result
|
||||||
|
responseTime = response.headers.get('request-duration');
|
||||||
|
response.headers.delete('request-duration');
|
||||||
|
} else {
|
||||||
|
// if it's not a network error, don't continue
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with the rest of the request lifecycle - post response vars, script, assertions, tests
|
||||||
|
|
||||||
const { data, dataBuffer } = parseDataFromResponse(response);
|
const { data, dataBuffer } = parseDataFromResponse(response);
|
||||||
response.data = data;
|
response.data = data;
|
||||||
@ -431,11 +460,6 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
|
||||||
// Prevents the duration on leaking to the actual result
|
|
||||||
const requestDuration = response.headers.get('request-duration');
|
|
||||||
response.headers.delete('request-duration');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
@ -443,94 +467,11 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
data: response.data,
|
data: response.data,
|
||||||
dataBuffer: dataBuffer.toString('base64'),
|
dataBuffer: dataBuffer.toString('base64'),
|
||||||
size: Buffer.byteLength(dataBuffer),
|
size: Buffer.byteLength(dataBuffer),
|
||||||
duration: requestDuration
|
duration: responseTime ?? 0
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// todo: better error handling
|
|
||||||
// need to convey the error to the UI
|
|
||||||
// and need not be always a network error
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
deleteCancelToken(cancelTokenUid);
|
||||||
|
|
||||||
if (axios.isCancel(error)) {
|
|
||||||
let error = new Error('Request cancelled');
|
|
||||||
error.isCancel = true;
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error?.response) {
|
|
||||||
const { data, dataBuffer } = parseDataFromResponse(error.response);
|
|
||||||
error.response.data = data;
|
|
||||||
// run assertions
|
|
||||||
const assertions = get(request, 'assertions');
|
|
||||||
if (assertions) {
|
|
||||||
const assertRuntime = new AssertRuntime();
|
|
||||||
const results = assertRuntime.runAssertions(
|
|
||||||
assertions,
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'assertion-results',
|
|
||||||
results: results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
const testFile = compact([
|
|
||||||
get(collectionRoot, 'request.tests'),
|
|
||||||
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
|
||||||
]).join(os.EOL);
|
|
||||||
if (typeof testFile === 'string') {
|
|
||||||
const testRuntime = new TestRuntime();
|
|
||||||
const testResults = await testRuntime.runTests(
|
|
||||||
decomment(testFile),
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
scriptingConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'test-results',
|
|
||||||
results: testResults.results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: testResults.envVariables,
|
|
||||||
collectionVariables: testResults.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevents the duration from leaking to the actual result
|
|
||||||
const requestDuration = error.response.headers.get('request-duration');
|
|
||||||
error.response.headers.delete('request-duration');
|
|
||||||
return {
|
|
||||||
status: error.response.status,
|
|
||||||
statusText: error.response.statusText,
|
|
||||||
headers: error.response.headers,
|
|
||||||
data: error.response.data,
|
|
||||||
dataBuffer: dataBuffer.toString('base64'),
|
|
||||||
size: Buffer.byteLength(dataBuffer),
|
|
||||||
duration: requestDuration ?? 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -553,9 +494,9 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const collectionRoot = get(collection, 'root', {});
|
const collectionRoot = get(collection, 'root', {});
|
||||||
const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request, collectionRoot);
|
const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request, collectionRoot);
|
||||||
|
|
||||||
request.timeout = preferences.getTimeout();
|
request.timeout = preferencesUtil.getRequestTimeout();
|
||||||
|
|
||||||
if (!preferences.isTlsVerification()) {
|
if (!preferencesUtil.shouldVerifyTls()) {
|
||||||
request.httpsAgent = new https.Agent({
|
request.httpsAgent = new https.Agent({
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
});
|
});
|
||||||
@ -564,7 +505,14 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const processEnvVars = getProcessEnvVars(collection.uid);
|
const processEnvVars = getProcessEnvVars(collection.uid);
|
||||||
interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars);
|
interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars);
|
||||||
|
|
||||||
const response = await axios(preparedRequest);
|
const axiosInstance = await configureRequest(
|
||||||
|
collection.uid,
|
||||||
|
preparedRequest,
|
||||||
|
envVars,
|
||||||
|
collection.collectionVariables,
|
||||||
|
processEnvVars
|
||||||
|
);
|
||||||
|
const response = await axiosInstance(preparedRequest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
@ -739,12 +687,56 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
timeStart = Date.now();
|
timeStart = Date.now();
|
||||||
/** @type {import('axios').AxiosResponse} */
|
let response;
|
||||||
const response = await axiosInstance(request);
|
try {
|
||||||
timeEnd = Date.now();
|
/** @type {import('axios').AxiosResponse} */
|
||||||
|
response = await axiosInstance(request);
|
||||||
|
timeEnd = Date.now();
|
||||||
|
|
||||||
const { data, dataBuffer } = parseDataFromResponse(response);
|
const { data, dataBuffer } = parseDataFromResponse(response);
|
||||||
response.data = data;
|
response.data = data;
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
|
type: 'response-received',
|
||||||
|
responseReceived: {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: Object.entries(response.headers),
|
||||||
|
duration: timeEnd - timeStart,
|
||||||
|
dataBuffer: dataBuffer.toString('base64'),
|
||||||
|
size: Buffer.byteLength(dataBuffer),
|
||||||
|
data: response.data
|
||||||
|
},
|
||||||
|
...eventData
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.response) {
|
||||||
|
const { data, dataBuffer } = parseDataFromResponse(error.response);
|
||||||
|
error.response.data = data;
|
||||||
|
|
||||||
|
timeEnd = Date.now();
|
||||||
|
response = {
|
||||||
|
status: error.response.status,
|
||||||
|
statusText: error.response.statusText,
|
||||||
|
headers: Object.entries(error.response.headers),
|
||||||
|
duration: timeEnd - timeStart,
|
||||||
|
dataBuffer: dataBuffer.toString('base64'),
|
||||||
|
size: Buffer.byteLength(dataBuffer),
|
||||||
|
data: error.response.data
|
||||||
|
};
|
||||||
|
|
||||||
|
// if we get a response from the server, we consider it as a success
|
||||||
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
|
type: 'response-received',
|
||||||
|
error: error ? error.message : 'An error occurred while running the request',
|
||||||
|
responseReceived: response,
|
||||||
|
...eventData
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if it's not a network error, don't continue
|
||||||
|
throw Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// run post-response vars
|
// run post-response vars
|
||||||
const postResponseVars = get(request, 'vars.res', []);
|
const postResponseVars = get(request, 'vars.res', []);
|
||||||
@ -847,108 +839,11 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
|
||||||
type: 'response-received',
|
|
||||||
...eventData,
|
|
||||||
responseReceived: {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
headers: Object.entries(response.headers),
|
|
||||||
duration: timeEnd - timeStart,
|
|
||||||
size: Buffer.byteLength(dataBuffer),
|
|
||||||
data: response.data
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let responseReceived = {};
|
|
||||||
let duration = 0;
|
|
||||||
|
|
||||||
if (timeStart && timeEnd) {
|
|
||||||
duration = timeEnd - timeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error?.response) {
|
|
||||||
const { data, dataBuffer } = parseDataFromResponse(error.response);
|
|
||||||
error.response.data = data;
|
|
||||||
|
|
||||||
responseReceived = {
|
|
||||||
status: error.response.status,
|
|
||||||
statusText: error.response.statusText,
|
|
||||||
headers: Object.entries(error.response.headers),
|
|
||||||
duration: duration,
|
|
||||||
size: Buffer.byteLength(dataBuffer),
|
|
||||||
data: error.response.data
|
|
||||||
};
|
|
||||||
|
|
||||||
// run assertions
|
|
||||||
const assertions = get(item, 'request.assertions');
|
|
||||||
if (assertions) {
|
|
||||||
const assertRuntime = new AssertRuntime();
|
|
||||||
const results = assertRuntime.runAssertions(
|
|
||||||
assertions,
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
|
||||||
type: 'assertion-results',
|
|
||||||
assertionResults: results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
const testFile = compact([
|
|
||||||
get(collectionRoot, 'request.tests'),
|
|
||||||
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
|
||||||
]).join(os.EOL);
|
|
||||||
if (typeof testFile === 'string') {
|
|
||||||
const testRuntime = new TestRuntime();
|
|
||||||
const testResults = await testRuntime.runTests(
|
|
||||||
decomment(testFile),
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
scriptingConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
|
||||||
type: 'test-results',
|
|
||||||
testResults: testResults.results,
|
|
||||||
...eventData
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: testResults.envVariables,
|
|
||||||
collectionVariables: testResults.collectionVariables,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we get a response from the server, we consider it as a success
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
|
||||||
type: 'response-received',
|
|
||||||
error: error ? error.message : 'An error occurred while running the request',
|
|
||||||
responseReceived: responseReceived,
|
|
||||||
...eventData
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
error: error ? error.message : 'An error occurred while running the request',
|
error: error ? error.message : 'An error occurred while running the request',
|
||||||
responseReceived: responseReceived,
|
responseReceived: {},
|
||||||
...eventData
|
...eventData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,64 +1,9 @@
|
|||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const { getPreferences, savePreferences, getPath } = require('../store/preferences');
|
const { getPreferences, savePreferences } = require('../store/preferences');
|
||||||
const { isDirectory } = require('../utils/filesystem');
|
const { isDirectory } = require('../utils/filesystem');
|
||||||
const { openCollection } = require('../app/collections');
|
const { openCollection } = require('../app/collections');
|
||||||
const stores = require('../store');
|
``;
|
||||||
const chokidar = require('chokidar');
|
|
||||||
|
|
||||||
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
const change = async (pathname, store) => {
|
|
||||||
if (store === stores.PREFERENCES) {
|
|
||||||
mainWindow.webContents.send('main:load-preferences', getPreferences());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class StoreWatcher {
|
|
||||||
constructor() {
|
|
||||||
this.watchers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
addWatcher(watchPath, store) {
|
|
||||||
console.log(`watcher add: ${watchPath} for store ${store}`);
|
|
||||||
|
|
||||||
if (this.watchers[watchPath]) {
|
|
||||||
this.watchers[watchPath].close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
setTimeout(() => {
|
|
||||||
const watcher = chokidar.watch(watchPath, {
|
|
||||||
ignoreInitial: false,
|
|
||||||
usePolling: false,
|
|
||||||
persistent: true,
|
|
||||||
ignorePermissionErrors: true,
|
|
||||||
awaitWriteFinish: {
|
|
||||||
stabilityThreshold: 80,
|
|
||||||
pollInterval: 10
|
|
||||||
},
|
|
||||||
depth: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher.on('change', (pathname) => change(pathname, store));
|
|
||||||
|
|
||||||
self.watchers[watchPath] = watcher;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasWatcher(watchPath) {
|
|
||||||
return this.watchers[watchPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
removeWatcher(watchPath) {
|
|
||||||
if (this.watchers[watchPath]) {
|
|
||||||
this.watchers[watchPath].close();
|
|
||||||
this.watchers[watchPath] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeWatcher = new StoreWatcher();
|
|
||||||
storeWatcher.addWatcher(getPath(), stores.PREFERENCES);
|
|
||||||
|
|
||||||
ipcMain.handle('renderer:ready', async (event) => {
|
ipcMain.handle('renderer:ready', async (event) => {
|
||||||
// load preferences
|
// load preferences
|
||||||
const preferences = getPreferences();
|
const preferences = getPreferences();
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
const PREFERENCES = 'PREFERENCES';
|
|
||||||
|
|
||||||
const stores = {
|
|
||||||
PREFERENCES
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = stores;
|
|
@ -19,14 +19,14 @@ const defaultPreferences = {
|
|||||||
proxy: {
|
proxy: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
hostnameHttp: '',
|
hostname: '',
|
||||||
portHttp: '',
|
port: '',
|
||||||
auth: {
|
auth: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
},
|
},
|
||||||
noProxy: ''
|
bypassProxy: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,13 +42,13 @@ const preferencesSchema = Yup.object().shape({
|
|||||||
enabled: Yup.boolean(),
|
enabled: Yup.boolean(),
|
||||||
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
|
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||||
hostname: Yup.string().max(1024),
|
hostname: Yup.string().max(1024),
|
||||||
port: Yup.number().min(1).max(65535),
|
port: Yup.number().min(1).max(65535).nullable(),
|
||||||
auth: Yup.object({
|
auth: Yup.object({
|
||||||
enabled: Yup.boolean(),
|
enabled: Yup.boolean(),
|
||||||
username: Yup.string().max(1024),
|
username: Yup.string().max(1024),
|
||||||
password: Yup.string().max(1024)
|
password: Yup.string().max(1024)
|
||||||
}).optional(),
|
}).optional(),
|
||||||
noProxy: Yup.string().optional().max(1024)
|
bypassProxy: Yup.string().optional().max(1024)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,10 +60,6 @@ class PreferencesStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPath() {
|
|
||||||
return this.store.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreferences() {
|
getPreferences() {
|
||||||
return {
|
return {
|
||||||
...defaultPreferences,
|
...defaultPreferences,
|
||||||
@ -96,20 +92,14 @@ const savePreferences = async (newPreferences) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPath = () => {
|
const preferencesUtil = {
|
||||||
return preferencesStore.getPath();
|
shouldVerifyTls: () => {
|
||||||
};
|
|
||||||
|
|
||||||
const preferences = {
|
|
||||||
isTlsVerification: () => {
|
|
||||||
return get(getPreferences(), 'request.sslVerification', true);
|
return get(getPreferences(), 'request.sslVerification', true);
|
||||||
},
|
},
|
||||||
|
getRequestTimeout: () => {
|
||||||
getTimeout: () => {
|
|
||||||
return get(getPreferences(), 'request.timeout', 0);
|
return get(getPreferences(), 'request.timeout', 0);
|
||||||
},
|
},
|
||||||
|
getGlobalProxyConfig: () => {
|
||||||
getProxyConfig: () => {
|
|
||||||
return get(getPreferences(), 'proxy', {});
|
return get(getPreferences(), 'proxy', {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -117,6 +107,5 @@ const preferences = {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
getPreferences,
|
getPreferences,
|
||||||
savePreferences,
|
savePreferences,
|
||||||
getPath,
|
preferencesUtil
|
||||||
preferences
|
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,8 @@ const Store = require('electron-store');
|
|||||||
const DEFAULT_WINDOW_WIDTH = 1280;
|
const DEFAULT_WINDOW_WIDTH = 1280;
|
||||||
const DEFAULT_WINDOW_HEIGHT = 768;
|
const DEFAULT_WINDOW_HEIGHT = 768;
|
||||||
|
|
||||||
|
const DEFAULT_MAXIMIZED = false;
|
||||||
|
|
||||||
class WindowStateStore {
|
class WindowStateStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.store = new Store({
|
this.store = new Store({
|
||||||
@ -25,6 +27,14 @@ class WindowStateStore {
|
|||||||
setBounds(bounds) {
|
setBounds(bounds) {
|
||||||
this.store.set('window-bounds', bounds);
|
this.store.set('window-bounds', bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaximized() {
|
||||||
|
return this.store.get('maximized') || DEFAULT_MAXIMIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaximized(isMaximized) {
|
||||||
|
this.store.set('maximized', isMaximized);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WindowStateStore;
|
module.exports = WindowStateStore;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const parseUrl = require('url').parse;
|
const parseUrl = require('url').parse;
|
||||||
|
const { isEmpty } = require('lodash');
|
||||||
|
|
||||||
const DEFAULT_PORTS = {
|
const DEFAULT_PORTS = {
|
||||||
ftp: 21,
|
ftp: 21,
|
||||||
@ -11,13 +12,14 @@ const DEFAULT_PORTS = {
|
|||||||
/**
|
/**
|
||||||
* check for proxy bypass, copied form 'proxy-from-env'
|
* check for proxy bypass, copied form 'proxy-from-env'
|
||||||
*/
|
*/
|
||||||
const shouldUseProxy = (url, proxyByPass) => {
|
const shouldUseProxy = (url, proxyBypass) => {
|
||||||
if (proxyByPass === '*') {
|
if (proxyBypass === '*') {
|
||||||
return false; // Never proxy if wildcard is set.
|
return false; // Never proxy if wildcard is set.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!proxyByPass) {
|
// use proxy if no proxyBypass is set
|
||||||
return true; // use proxy if enabled
|
if (!proxyBypass || typeof proxyBypass !== 'string' || isEmpty(proxyBypass.trim())) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
|
const parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
|
||||||
@ -34,7 +36,7 @@ const shouldUseProxy = (url, proxyByPass) => {
|
|||||||
hostname = hostname.replace(/:\d*$/, '');
|
hostname = hostname.replace(/:\d*$/, '');
|
||||||
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
|
port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
|
||||||
|
|
||||||
return proxyByPass.split(/[,;\s]/).every(function (dontProxyFor) {
|
return proxyBypass.split(/[,;\s]/).every(function (dontProxyFor) {
|
||||||
if (!dontProxyFor) {
|
if (!dontProxyFor) {
|
||||||
return true; // Skip zero-length hosts.
|
return true; // Skip zero-length hosts.
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,14 @@ const DEFAULT_WINDOW_WIDTH = 1280;
|
|||||||
const DEFAULT_WINDOW_HEIGHT = 768;
|
const DEFAULT_WINDOW_HEIGHT = 768;
|
||||||
|
|
||||||
const loadWindowState = () => {
|
const loadWindowState = () => {
|
||||||
|
const maximized = windowStateStore.getMaximized();
|
||||||
const bounds = windowStateStore.getBounds();
|
const bounds = windowStateStore.getBounds();
|
||||||
|
|
||||||
const positionValid = isPositionValid(bounds);
|
const positionValid = isPositionValid(bounds);
|
||||||
const sizeValid = isSizeValid(bounds);
|
const sizeValid = isSizeValid(bounds);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
maximized,
|
||||||
x: bounds.x && positionValid ? bounds.x : undefined,
|
x: bounds.x && positionValid ? bounds.x : undefined,
|
||||||
y: bounds.y && positionValid ? bounds.y : undefined,
|
y: bounds.y && positionValid ? bounds.y : undefined,
|
||||||
width: bounds.width && sizeValid ? bounds.width : DEFAULT_WINDOW_WIDTH,
|
width: bounds.width && sizeValid ? bounds.width : DEFAULT_WINDOW_WIDTH,
|
||||||
@ -20,12 +22,16 @@ const loadWindowState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveWindowState = (window) => {
|
const saveBounds = (window) => {
|
||||||
const bounds = window.getBounds();
|
const bounds = window.getBounds();
|
||||||
|
|
||||||
windowStateStore.setBounds(bounds);
|
windowStateStore.setBounds(bounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveMaximized = (isMaximized) => {
|
||||||
|
windowStateStore.setMaximized(isMaximized);
|
||||||
|
};
|
||||||
|
|
||||||
const isPositionValid = (bounds) => {
|
const isPositionValid = (bounds) => {
|
||||||
const area = getArea(bounds);
|
const area = getArea(bounds);
|
||||||
|
|
||||||
@ -49,5 +55,6 @@ const getArea = (bounds) => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadWindowState,
|
loadWindowState,
|
||||||
saveWindowState
|
saveBounds,
|
||||||
|
saveMaximized
|
||||||
};
|
};
|
||||||
|
55
packages/bruno-js/src/interpolate-string.js
Normal file
55
packages/bruno-js/src/interpolate-string.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const Handlebars = require('handlebars');
|
||||||
|
const { forOwn, cloneDeep } = require('lodash');
|
||||||
|
|
||||||
|
const interpolateEnvVars = (str, processEnvVars) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
return template({
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const interpolateString = (str, { envVariables, collectionVariables, processEnvVars }) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
processEnvVars = processEnvVars || {};
|
||||||
|
collectionVariables = collectionVariables || {};
|
||||||
|
|
||||||
|
// we clone envVariables because we don't want to modify the original object
|
||||||
|
envVariables = envVariables ? cloneDeep(envVariables) : {};
|
||||||
|
|
||||||
|
// envVariables can inturn have values as {{process.env.VAR_NAME}}
|
||||||
|
// so we need to interpolate envVariables first with processEnvVars
|
||||||
|
forOwn(envVariables, (value, key) => {
|
||||||
|
envVariables[key] = interpolateEnvVars(value, processEnvVars);
|
||||||
|
});
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
// collectionVariables take precedence over envVariables
|
||||||
|
const combinedVars = {
|
||||||
|
...envVariables,
|
||||||
|
...collectionVariables,
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return template(combinedVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interpolateString
|
||||||
|
};
|
@ -4,6 +4,7 @@ const { nanoid } = require('nanoid');
|
|||||||
const Bru = require('../bru');
|
const Bru = require('../bru');
|
||||||
const BrunoRequest = require('../bruno-request');
|
const BrunoRequest = require('../bruno-request');
|
||||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||||
|
const { interpolateString } = require('../interpolate-string');
|
||||||
|
|
||||||
const { expect } = chai;
|
const { expect } = chai;
|
||||||
chai.use(require('chai-string'));
|
chai.use(require('chai-string'));
|
||||||
@ -161,17 +162,27 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interpolationContext = {
|
||||||
|
collectionVariables: context.bru.collectionVariables,
|
||||||
|
envVariables: context.bru.envVariables,
|
||||||
|
processEnvVars: context.bru.processEnvVars
|
||||||
|
};
|
||||||
|
|
||||||
// gracefully allow both a,b as well as [a, b]
|
// gracefully allow both a,b as well as [a, b]
|
||||||
if (operator === 'in' || operator === 'notIn') {
|
if (operator === 'in' || operator === 'notIn') {
|
||||||
if (rhsOperand.startsWith('[') && rhsOperand.endsWith(']')) {
|
if (rhsOperand.startsWith('[') && rhsOperand.endsWith(']')) {
|
||||||
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
|
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context));
|
return rhsOperand
|
||||||
|
.split(',')
|
||||||
|
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operator === 'between') {
|
if (operator === 'between') {
|
||||||
const [lhs, rhs] = rhsOperand.split(',').map((v) => evaluateJsTemplateLiteral(v.trim(), context));
|
const [lhs, rhs] = rhsOperand
|
||||||
|
.split(',')
|
||||||
|
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||||
return [lhs, rhs];
|
return [lhs, rhs];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,10 +192,10 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
|||||||
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
|
rhsOperand = rhsOperand.substring(1, rhsOperand.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rhsOperand;
|
return interpolateString(rhsOperand, interpolationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return evaluateJsTemplateLiteral(rhsOperand, context);
|
return evaluateJsTemplateLiteral(interpolateString(rhsOperand, interpolationContext), context);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AssertRuntime {
|
class AssertRuntime {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
**English** | [Русский](/readme_ru.md)
|
**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md)
|
||||||
|
|
||||||
Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there.
|
Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there.
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
[English](/readme.md) | **Русский**
|
[English](/readme.md) | [Українська](/readme_ua.md) | **Русский**
|
||||||
|
|
||||||
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
||||||
|
|
||||||
|
80
readme_ua.md
Normal file
80
readme_ua.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<br />
|
||||||
|
<img src="assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
|
### Bruno - IDE із відкритим кодом для тестування та дослідження API
|
||||||
|
|
||||||
|
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
|
||||||
|
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
|
||||||
|
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
|
||||||
|
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
|
||||||
|
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
|
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
[English](/readme.md) | **Українська** | [Русский](/readme_ru.md)
|
||||||
|
|
||||||
|
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
|
||||||
|
|
||||||
|
Bruno зберігає ваші колекції напряму у теці на вашому диску. Він використовує текстову мову розмітки Bru для збереження інформації про ваші API запити.
|
||||||
|
|
||||||
|
Ви можете використовувати git або будь-яку іншу систему контролю версій щоб спільно працювати над вашими колекціями API запитів.
|
||||||
|
|
||||||
|
Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
|
![bruno](assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
|
### Кросплатформенність 🖥️
|
||||||
|
|
||||||
|
![bruno](assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
|
### Спільна робота через Git 👩💻🧑💻
|
||||||
|
|
||||||
|
Або будь-яку іншу систему контролю версій на ваш вибір
|
||||||
|
|
||||||
|
![bruno](assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
|
### Важливі посилання 📌
|
||||||
|
|
||||||
|
- [Наше бачення довготривалої перспективи проекту](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
- [Дорожня карта проекту](https://github.com/usebruno/bruno/discussions/384)
|
||||||
|
- [Документація](https://docs.usebruno.com)
|
||||||
|
- [Сайт](https://www.usebruno.com)
|
||||||
|
- [Завантаження](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
### Вітрина 🎥
|
||||||
|
|
||||||
|
- [Відгуки](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
- [Хаб знань](https://github.com/usebruno/bruno/discussions/386)
|
||||||
|
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||||
|
|
||||||
|
### Підтримка ❤️
|
||||||
|
|
||||||
|
Гав! Якщо вам сподобався проект, тисніть на ⭐ !!
|
||||||
|
|
||||||
|
### Поділитись відгуками 📣
|
||||||
|
|
||||||
|
Якщо Bruno допоміг вам у вашій роботі і вашим командам, будь ласка не забудьте поділитись вашими [відгуками у github дискусії](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
|
||||||
|
### Зробити свій внесок 👩💻🧑💻
|
||||||
|
|
||||||
|
Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing_ua.md)
|
||||||
|
|
||||||
|
Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі.
|
||||||
|
|
||||||
|
### Автори
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Залишайтесь на зв'язку 🌐
|
||||||
|
|
||||||
|
[Twitter](https://twitter.com/use_bruno) <br />
|
||||||
|
[Сайт](https://www.usebruno.com) <br />
|
||||||
|
[Discord](https://discord.com/invite/KgcZUncpjq)
|
||||||
|
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
||||||
|
|
||||||
|
### Ліцензія 📄
|
||||||
|
|
||||||
|
[MIT](license.md)
|
Loading…
Reference in New Issue
Block a user