mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-26 09:53:50 +01:00
Merge branch 'main' into feature/add-bru-setNextRequest
This commit is contained in:
commit
4a1d45f458
@ -1,12 +1,12 @@
|
|||||||
**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md)
|
**English** | [Українська](docs/contributing/contributing_ua.md) | [Русский](docs/contributing/contributing_ru.md) | [Türkçe](docs/contributing/contributing_tr.md) | [Deutsch](docs/contributing/contributing_de.md) | [Français](docs/contributing/contributing_fr.md)
|
||||||
|
|
||||||
## Lets make bruno better, together !!
|
## Let's make bruno better, together !!
|
||||||
|
|
||||||
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
|
We are happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
|
||||||
|
|
||||||
### Technology Stack
|
### Technology Stack
|
||||||
|
|
||||||
Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections)
|
Bruno is built using Next.js and React. We also use electron to ship a desktop version (that supports local collections)
|
||||||
|
|
||||||
Libraries we use
|
Libraries we use
|
||||||
|
|
||||||
@ -23,9 +23,59 @@ Libraries we use
|
|||||||
|
|
||||||
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
|
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
|
||||||
|
|
||||||
### Lets start coding
|
## Development
|
||||||
|
|
||||||
Please reference [development.md](docs/development.md) for instructions on running the local development environment.
|
Bruno is being developed as a desktop app. You need to load the app by running the Next.js app in one terminal and then run the electron app in another terminal.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# use nodejs 18 version
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# install deps
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# build graphql docs
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# build bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# run next app (terminal 1)
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# run electron app (terminal 2)
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Delete node_modules in sub-directories
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Delete package-lock in sub-directories
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
||||||
|
|
||||||
### Raising Pull Request
|
### Raising Pull Request
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | **Deutsch**
|
|
||||||
|
|
||||||
## Lass uns Bruno noch besser machen, gemeinsam !!
|
|
||||||
|
|
||||||
Ich freue mich, dass Du Bruno verbessern möchtest. Hier findest Du eine Anleitung, mit der Du Bruno auf Deinem Computer einrichten kannst.
|
|
||||||
|
|
||||||
### Technologie Stack
|
|
||||||
|
|
||||||
Bruno ist mit NextJs und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt).
|
|
||||||
|
|
||||||
Bibliotheken die wir benutzen
|
|
||||||
|
|
||||||
- CSS - Tailwind
|
|
||||||
- Code Editoren - Codemirror
|
|
||||||
- State Management - Redux
|
|
||||||
- Icons - Tabler Icons
|
|
||||||
- Formulare - formik
|
|
||||||
- Schema Validierung - Yup
|
|
||||||
- Request Client - axios
|
|
||||||
- Dateisystem Watcher - chokidar
|
|
||||||
|
|
||||||
### Abhängigkeiten
|
|
||||||
|
|
||||||
Du benötigst [Node v18.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
|
||||||
|
|
||||||
### Lass uns coden
|
|
||||||
|
|
||||||
Eine Anleitung zum Ausführen einer lokalen Entwicklungsumgebung findest Du in [development.md](docs/development_de.md).
|
|
||||||
|
|
||||||
### Pull Request erstellen
|
|
||||||
|
|
||||||
- Bitte halte die PRs klein und begrenzt auf eine Sache
|
|
||||||
- Bitte halte Dich beim Erstellen eines Branches an das folgende Format
|
|
||||||
- feature/[feature name]: Dieser Branch soll Änderungen für ein bestimmtes Feature enthalten
|
|
||||||
- Beispiel: feature/dark-mode
|
|
||||||
- bugfix/[bug name]: Dieser Branch soll ausschließlich Bugfixes für einen bestimmten Bug enthalten
|
|
||||||
- Beispiel: bugfix/bug-1
|
|
@ -1,37 +0,0 @@
|
|||||||
[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md)
|
|
||||||
|
|
||||||
## Давайте вместе сделаем Бруно лучше!!!
|
|
||||||
|
|
||||||
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
|
|
||||||
|
|
||||||
### Стек
|
|
||||||
|
|
||||||
Bruno построен с использованием NextJs и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
|
|
||||||
|
|
||||||
Библиотеки, которые мы используем
|
|
||||||
|
|
||||||
- CSS - Tailwind
|
|
||||||
- Редакторы кода - Codemirror
|
|
||||||
- Управление состоянием - Redux
|
|
||||||
- Иконки - Tabler Icons
|
|
||||||
- Формы - formik
|
|
||||||
- Валидация схем - Yup
|
|
||||||
- Запросы клиента - axios
|
|
||||||
- Наблюдатель за файловой системой - chokidar
|
|
||||||
|
|
||||||
### Зависимости
|
|
||||||
|
|
||||||
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
|
|
||||||
|
|
||||||
### Приступим к коду
|
|
||||||
|
|
||||||
Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки.
|
|
||||||
|
|
||||||
### Создание Pull Request
|
|
||||||
|
|
||||||
- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи
|
|
||||||
- Пожалуйста, соблюдайте формат создания веток
|
|
||||||
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
|
|
||||||
- Пример: feature/dark-mode
|
|
||||||
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
|
|
||||||
- Пример bugfix/bug-1
|
|
@ -1,37 +0,0 @@
|
|||||||
[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.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
|
|
89
docs/contributing/contributing_de.md
Normal file
89
docs/contributing/contributing_de.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
## Lass uns Bruno noch besser machen, gemeinsam !!
|
||||||
|
|
||||||
|
Ich freue mich, dass Du Bruno verbessern möchtest. Hier findest Du eine Anleitung, mit der Du Bruno auf Deinem Computer einrichten kannst.
|
||||||
|
|
||||||
|
### Technologie Stack
|
||||||
|
|
||||||
|
Bruno ist mit Next.js und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt).
|
||||||
|
|
||||||
|
Bibliotheken die wir benutzen
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Code Editoren - Codemirror
|
||||||
|
- State Management - Redux
|
||||||
|
- Icons - Tabler Icons
|
||||||
|
- Formulare - formik
|
||||||
|
- Schema Validierung - Yup
|
||||||
|
- Request Client - axios
|
||||||
|
- Dateisystem Watcher - chokidar
|
||||||
|
|
||||||
|
### Abhängigkeiten
|
||||||
|
|
||||||
|
Du benötigst [Node v18.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
||||||
|
|
||||||
|
### Lass uns coden
|
||||||
|
|
||||||
|
Eine Anleitung zum Ausführen einer lokalen Entwicklungsumgebung findest Du in [development.md](docs/development_de.md).
|
||||||
|
|
||||||
|
### Pull Request erstellen
|
||||||
|
|
||||||
|
- Bitte halte die PRs klein und begrenzt auf eine Sache
|
||||||
|
- Bitte halte Dich beim Erstellen eines Branches an das folgende Format
|
||||||
|
- feature/[feature name]: Dieser Branch soll Änderungen für ein bestimmtes Feature enthalten
|
||||||
|
- Beispiel: feature/dark-mode
|
||||||
|
- bugfix/[bug name]: Dieser Branch soll ausschließlich Bugfixes für einen bestimmten Bug enthalten
|
||||||
|
- Beispiel: bugfix/bug-1
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
|
Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die Next.js App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App.
|
||||||
|
|
||||||
|
### Abhängigkeiten
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Lokales Entwickeln
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# use nodejs 18 version
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# install deps
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# build graphql docs
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# build bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# run next app (terminal 1)
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# run electron app (terminal 2)
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
Es kann sein, dass Du einen `Unsupported platform`-Fehler bekommst, wenn Du `npm install` ausführst. Um dies zu beheben, musst Du `node_modules` und `package-lock.json` löschen und `npm install` erneut ausführen. Dies sollte alle notwendigen Pakete installieren, die zum Ausführen der Anwendung benötigt werden.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Delete node_modules in sub-directories
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Delete package-lock in sub-directories
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
89
docs/contributing/contributing_fr.md
Normal file
89
docs/contributing/contributing_fr.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
## Ensemble, améliorons Bruno !
|
||||||
|
|
||||||
|
Je suis content de voir que vous envisagez améliorer Bruno. Ci-dessous, vous trouverez les règles et guides pour récupérer Bruno sur votre ordinateur.
|
||||||
|
|
||||||
|
### Technologies utilisées
|
||||||
|
|
||||||
|
Bruno est construit en utilisant NextJs et React. Nous utilisons aussi Electron pour embarquer la version ordinateur (qui permet les collections locales).
|
||||||
|
|
||||||
|
Les bibliothèques que nous utilisons :
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Code Editors - Codemirror
|
||||||
|
- State Management - Redux
|
||||||
|
- Icons - Tabler Icons
|
||||||
|
- Forms - formik
|
||||||
|
- Schema Validation - Yup
|
||||||
|
- Request Client - axios
|
||||||
|
- Filesystem Watcher - chokidar
|
||||||
|
|
||||||
|
### Dépendances
|
||||||
|
|
||||||
|
Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
|
||||||
|
|
||||||
|
### Commençons à coder
|
||||||
|
|
||||||
|
Veuillez vous référez à la [documentation de développement](docs/development_fr.md) pour les instructions de démarrage de l'environnement de développement local.
|
||||||
|
|
||||||
|
### Ouvrir une Pull Request
|
||||||
|
|
||||||
|
- Merci de conserver les PR petites et focalisées sur un seul objectif
|
||||||
|
- Merci de suivre le format de nom des branches
|
||||||
|
- feature/[feature name]: Cette branche devrait contenir une fonctionnalité spécifique
|
||||||
|
- Exemple: feature/dark-mode
|
||||||
|
- bugfix/[bug name]: Cette branche devrait contenir seulement une solution pour pour une bogue spécifique
|
||||||
|
- Exemple: bugfix/bug-1
|
||||||
|
|
||||||
|
## Développement
|
||||||
|
|
||||||
|
Bruno est développé comme une application de _lourde_. Vous devez charger l'application en démarrant nextjs dans un terminal, puis démarre l'application Electron dans un autre terminal.
|
||||||
|
|
||||||
|
### Dépendances
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Développement local
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# use nodejs 18 version
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# install deps
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# build graphql docs
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# build bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# run next app (terminal 1)
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# run electron app (terminal 2)
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dépannage
|
||||||
|
|
||||||
|
Vous pourriez rencontrer une error `Unsupported platform` pendant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules`, le fichier `package-lock.json` et lancer à nouveau `npm install`. Cela devrait isntaller tous les paquets nécessaires pour lancer l'application.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Delete node_modules in sub-directories
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Delete package-lock in sub-directories
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
89
docs/contributing/contributing_ru.md
Normal file
89
docs/contributing/contributing_ru.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
## Давайте вместе сделаем Бруно лучше!!!
|
||||||
|
|
||||||
|
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
|
||||||
|
|
||||||
|
### Стек
|
||||||
|
|
||||||
|
Bruno построен с использованием Next.js и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
|
||||||
|
|
||||||
|
Библиотеки, которые мы используем
|
||||||
|
|
||||||
|
- CSS - Tailwind
|
||||||
|
- Редакторы кода - Codemirror
|
||||||
|
- Управление состоянием - Redux
|
||||||
|
- Иконки - Tabler Icons
|
||||||
|
- Формы - formik
|
||||||
|
- Валидация схем - Yup
|
||||||
|
- Запросы клиента - axios
|
||||||
|
- Наблюдатель за файловой системой - chokidar
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
|
||||||
|
|
||||||
|
### Приступим к коду
|
||||||
|
|
||||||
|
Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки.
|
||||||
|
|
||||||
|
### Создание Pull Request
|
||||||
|
|
||||||
|
- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи
|
||||||
|
- Пожалуйста, соблюдайте формат создания веток
|
||||||
|
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
|
||||||
|
- Пример: feature/dark-mode
|
||||||
|
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
|
||||||
|
- Пример bugfix/bug-1
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение Next.js в одном терминале, а затем запустить приложение electron в другом терминале.
|
||||||
|
|
||||||
|
### Зависимости
|
||||||
|
|
||||||
|
- NodeJS v18
|
||||||
|
|
||||||
|
### Локальная разработка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# используйте nodejs 18 версии
|
||||||
|
nvm use
|
||||||
|
|
||||||
|
# установите зависимости
|
||||||
|
npm i --legacy-peer-deps
|
||||||
|
|
||||||
|
# билд документации по graphql
|
||||||
|
npm run build:graphql-docs
|
||||||
|
|
||||||
|
# билд bruno query
|
||||||
|
npm run build:bruno-query
|
||||||
|
|
||||||
|
# запустить next приложение ( терминал 1 )
|
||||||
|
npm run dev:web
|
||||||
|
|
||||||
|
# запустить приложение electron ( терминал 2 )
|
||||||
|
npm run dev:electron
|
||||||
|
```
|
||||||
|
|
||||||
|
### Устранение неисправностей
|
||||||
|
|
||||||
|
При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Удаление node_modules в подкаталогах
|
||||||
|
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||||
|
rm -rf "$dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Удаление package-lock в подкаталогах
|
||||||
|
find . -type f -name "package-lock.json" -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестирование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# bruno-schema
|
||||||
|
npm test --workspace=packages/bruno-schema
|
||||||
|
|
||||||
|
# bruno-lang
|
||||||
|
npm test --workspace=packages/bruno-lang
|
||||||
|
```
|
@ -1,12 +1,10 @@
|
|||||||
[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** | [Deutsch](/contributing_de.md)
|
|
||||||
|
|
||||||
## Bruno'yu birlikte daha iyi hale getirelim !!
|
## Bruno'yu birlikte daha iyi hale getirelim !!
|
||||||
|
|
||||||
Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır.
|
Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır.
|
||||||
|
|
||||||
### Kullanılan Teknolojiler
|
### Kullanılan Teknolojiler
|
||||||
|
|
||||||
Bruno, NextJs ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz
|
Bruno, Next.js ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz
|
||||||
|
|
||||||
Kullandığımız kütüphaneler
|
Kullandığımız kütüphaneler
|
||||||
|
|
89
docs/contributing/contributing_ua.md
Normal file
89
docs/contributing/contributing_ua.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
## Давайте зробимо Bruno краще, разом !!
|
||||||
|
|
||||||
|
Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері.
|
||||||
|
|
||||||
|
### Стек технологій
|
||||||
|
|
||||||
|
Bruno побудований на Next.js та 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
|
||||||
|
|
||||||
|
## Розробка
|
||||||
|
|
||||||
|
Bruno розробляється як декстопний застосунок. Вам потрібно запустити Next.js в одній сесії терміналу, та запустити застосунок 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
|
||||||
|
```
|
@ -1,55 +0,0 @@
|
|||||||
**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md)
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
- NodeJS v18
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# use nodejs 18 version
|
|
||||||
nvm use
|
|
||||||
|
|
||||||
# install deps
|
|
||||||
npm i --legacy-peer-deps
|
|
||||||
|
|
||||||
# build graphql docs
|
|
||||||
npm run build:graphql-docs
|
|
||||||
|
|
||||||
# build bruno query
|
|
||||||
npm run build:bruno-query
|
|
||||||
|
|
||||||
# run next app (terminal 1)
|
|
||||||
npm run dev:web
|
|
||||||
|
|
||||||
# run electron app (terminal 2)
|
|
||||||
npm run dev:electron
|
|
||||||
```
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Delete node_modules in sub-directories
|
|
||||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
|
||||||
rm -rf "$dir"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Delete package-lock in sub-directories
|
|
||||||
find . -type f -name "package-lock.json" -delete
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# bruno-schema
|
|
||||||
npm test --workspace=packages/bruno-schema
|
|
||||||
|
|
||||||
# bruno-lang
|
|
||||||
npm test --workspace=packages/bruno-lang
|
|
||||||
```
|
|
@ -1,55 +0,0 @@
|
|||||||
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | **Deutsch**
|
|
||||||
|
|
||||||
## Entwicklung
|
|
||||||
|
|
||||||
Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die NextJs-App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App.
|
|
||||||
|
|
||||||
### Abhängigkeiten
|
|
||||||
|
|
||||||
- NodeJS v18
|
|
||||||
|
|
||||||
### Lokales Entwickeln
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# use nodejs 18 version
|
|
||||||
nvm use
|
|
||||||
|
|
||||||
# install deps
|
|
||||||
npm i --legacy-peer-deps
|
|
||||||
|
|
||||||
# build graphql docs
|
|
||||||
npm run build:graphql-docs
|
|
||||||
|
|
||||||
# build bruno query
|
|
||||||
npm run build:bruno-query
|
|
||||||
|
|
||||||
# run next app (terminal 1)
|
|
||||||
npm run dev:web
|
|
||||||
|
|
||||||
# run electron app (terminal 2)
|
|
||||||
npm run dev:electron
|
|
||||||
```
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
Es kann sein, dass Du einen `Unsupported platform`-Fehler bekommst, wenn Du `npm install` ausführst. Um dies zu beheben, musst Du `node_modules` und `package-lock.json` löschen und `npm install` erneut ausführen. Dies sollte alle notwendigen Pakete installieren, die zum Ausführen der Anwendung benötigt werden.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Delete node_modules in sub-directories
|
|
||||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
|
||||||
rm -rf "$dir"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Delete package-lock in sub-directories
|
|
||||||
find . -type f -name "package-lock.json" -delete
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# bruno-schema
|
|
||||||
npm test --workspace=packages/bruno-schema
|
|
||||||
|
|
||||||
# bruno-lang
|
|
||||||
npm test --workspace=packages/bruno-lang
|
|
||||||
```
|
|
@ -1,55 +0,0 @@
|
|||||||
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский** | [Deutsch](/docs/development_de.md)
|
|
||||||
|
|
||||||
## Разработка
|
|
||||||
|
|
||||||
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение electron в другом терминале.
|
|
||||||
|
|
||||||
### Зависимости
|
|
||||||
|
|
||||||
- NodeJS v18
|
|
||||||
|
|
||||||
### Локальная разработка
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# используйте nodejs 18 версии
|
|
||||||
nvm use
|
|
||||||
|
|
||||||
# установите зависимости
|
|
||||||
npm i --legacy-peer-deps
|
|
||||||
|
|
||||||
# билд документации по graphql
|
|
||||||
npm run build:graphql-docs
|
|
||||||
|
|
||||||
# билд bruno query
|
|
||||||
npm run build:bruno-query
|
|
||||||
|
|
||||||
# запустить next приложение ( терминал 1 )
|
|
||||||
npm run dev:web
|
|
||||||
|
|
||||||
# запустить приложение electron ( терминал 2 )
|
|
||||||
npm run dev:electron
|
|
||||||
```
|
|
||||||
|
|
||||||
### Устранение неисправностей
|
|
||||||
|
|
||||||
При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Удаление node_modules в подкаталогах
|
|
||||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
|
||||||
rm -rf "$dir"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Удаление package-lock в подкаталогах
|
|
||||||
find . -type f -name "package-lock.json" -delete
|
|
||||||
```
|
|
||||||
|
|
||||||
### Тестирование
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# bruno-schema
|
|
||||||
npm test --workspace=packages/bruno-schema
|
|
||||||
|
|
||||||
# bruno-lang
|
|
||||||
npm test --workspace=packages/bruno-lang
|
|
||||||
```
|
|
@ -1,55 +0,0 @@
|
|||||||
[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.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
|
|
||||||
```
|
|
@ -10,8 +10,6 @@
|
|||||||
[![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) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | **Deutsch**
|
|
||||||
|
|
||||||
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.
|
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.
|
||||||
|
|
||||||
Bruno speichert Deine Sammlungen direkt in einem Ordner in Deinem Dateisystem. Wir verwenden eine einfache Textauszeichnungssprache - Bru - um Informationen über API-Anfragen zu speichern.
|
Bruno speichert Deine Sammlungen direkt in einem Ordner in Deinem Dateisystem. Wir verwenden eine einfache Textauszeichnungssprache - Bru - um Informationen über API-Anfragen zu speichern.
|
||||||
@ -61,7 +59,7 @@ Bitte [hier](publishing.md) für mehr Informationen lesen.
|
|||||||
|
|
||||||
### Mitmachen 👩💻🧑💻
|
### Mitmachen 👩💻🧑💻
|
||||||
|
|
||||||
Ich freue mich, dass Du Bruno verbessern willst. Bitte schau Dir den [Leitfaden zum Mitmachen](contributing_de.md) an.
|
Ich freue mich, dass Du Bruno verbessern willst. Bitte schau Dir den [Leitfaden zum Mitmachen](../contributing/contributing_de.md) an.
|
||||||
|
|
||||||
Auch wenn Du nicht in der Lage bist, einen Beitrag in Form von Code zu leisten, zögere bitte nicht, uns Fehler und Funktionswünsche mitzuteilen, die implementiert werden müssen, um Deinen Anwendungsfall zu unterstützen.
|
Auch wenn Du nicht in der Lage bist, einen Beitrag in Form von Code zu leisten, zögere bitte nicht, uns Fehler und Funktionswünsche mitzuteilen, die implementiert werden müssen, um Deinen Anwendungsfall zu unterstützen.
|
||||||
|
|
94
docs/readme/readme_fr.md
Normal file
94
docs/readme/readme_fr.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<br />
|
||||||
|
<img src="assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
|
### Bruno - IDE Opensource pour explorer et tester des APIs.
|
||||||
|
|
||||||
|
[![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)
|
||||||
|
|
||||||
|
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _status quo_ que représente Postman et les autres outils.
|
||||||
|
|
||||||
|
Bruno sauvegarde vos collections directement sur votre système de fichiers. Nous utilisons un langage de balise de type texte pour décrire les requêtes API.
|
||||||
|
|
||||||
|
Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler de manière collaborative sur vos collections d'APIs.
|
||||||
|
|
||||||
|
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
|
||||||
|
|
||||||
|
![bruno](assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
|
### Fonctionne sur de multiples platformes 🖥️
|
||||||
|
|
||||||
|
![bruno](assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
|
### Collaborer via Git 👩💻🧑💻
|
||||||
|
|
||||||
|
Ou n'importe quel système de gestion de sources
|
||||||
|
|
||||||
|
![bruno](assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
|
### Liens importants 📌
|
||||||
|
|
||||||
|
- [Notre vision à long terme (en anglais)](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
|
||||||
|
- [Documentation](https://docs.usebruno.com)
|
||||||
|
- [Site web](https://www.usebruno.com)
|
||||||
|
- [Prix](https://www.usebruno.com/pricing)
|
||||||
|
- [Téléchargement](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
|
### Showcase 🎥
|
||||||
|
|
||||||
|
- [Témoignages](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
- [Centre de connaissance](https://github.com/usebruno/bruno/discussions/386)
|
||||||
|
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||||
|
|
||||||
|
### Soutien ❤️
|
||||||
|
|
||||||
|
Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !!
|
||||||
|
|
||||||
|
### Partage de témoignages 📣
|
||||||
|
|
||||||
|
Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion Github dédiée](https://github.com/usebruno/bruno/discussions/343)
|
||||||
|
|
||||||
|
### Publier Bruno sur un nouveau gestionnaire de paquets
|
||||||
|
|
||||||
|
Veuillez regarder [ici](publishing.md) pour plus d'information.
|
||||||
|
|
||||||
|
### Contribuer 👩💻🧑💻
|
||||||
|
|
||||||
|
Je suis heureux de voir que vous cherchez à améliorer Bruno. Merci de consulter le [guide de contribution](../contributing/contributing_fr.md)
|
||||||
|
|
||||||
|
Même si vous n'êtes pas en mesure de contribuer directement via du code, n'hésitez pas à consigner les bogues et les demandes de nouvelles fonctionnalités pour résoudre vos cas d'usage !
|
||||||
|
|
||||||
|
### Auteurs
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Restons en contact 🌐
|
||||||
|
|
||||||
|
[Twitter](https://twitter.com/use_bruno) <br />
|
||||||
|
[Website](https://www.usebruno.com) <br />
|
||||||
|
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
|
||||||
|
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
||||||
|
|
||||||
|
### Marque
|
||||||
|
|
||||||
|
**Nom**
|
||||||
|
|
||||||
|
`Bruno` est une marque appartenant à [Anoop M D](https://www.helloanoop.com/)
|
||||||
|
|
||||||
|
**Logo**
|
||||||
|
|
||||||
|
Le logo est issu de [OpenMoji](https://openmoji.org/library/emoji-1F436/).
|
||||||
|
Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||||
|
|
||||||
|
### Licence 📄
|
||||||
|
|
||||||
|
[MIT](license.md)
|
@ -10,8 +10,6 @@
|
|||||||
[![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) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md)
|
|
||||||
|
|
||||||
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
||||||
|
|
||||||
Bruno хранит ваши коллекции непосредственно в папке в вашей файловой системе. Для сохранения информации об API-запросах мы используем язык Bru.
|
Bruno хранит ваши коллекции непосредственно в папке в вашей файловой системе. Для сохранения информации об API-запросах мы используем язык Bru.
|
||||||
@ -56,7 +54,7 @@ Bruno работает только в автономном режиме. Доб
|
|||||||
|
|
||||||
### Внести вклад 👩💻🧑💻
|
### Внести вклад 👩💻🧑💻
|
||||||
|
|
||||||
Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](contributing_ru.md)
|
Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](../contributing/contributing_ru.md)
|
||||||
|
|
||||||
Даже если вы не можете внести свой вклад с помощью кода, пожалуйста, не стесняйтесь сообщать об ошибках и пожеланиях к функциям, которые необходимо реализовать для решения вашей задачи.
|
Даже если вы не можете внести свой вклад с помощью кода, пожалуйста, не стесняйтесь сообщать об ошибках и пожеланиях к функциям, которые необходимо реализовать для решения вашей задачи.
|
||||||
|
|
@ -10,8 +10,6 @@
|
|||||||
[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
|
||||||
[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
|
||||||
|
|
||||||
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | **Türkçe** | [Deutsch](/readme_de.md)
|
|
||||||
|
|
||||||
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.
|
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.
|
||||||
|
|
||||||
Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz.
|
Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz.
|
||||||
@ -56,7 +54,7 @@ Bruno işinizde ve ekiplerinizde size yardımcı olduysa, lütfen [github tartı
|
|||||||
|
|
||||||
### Katkıda Bulunun 👩💻🧑💻
|
### Katkıda Bulunun 👩💻🧑💻
|
||||||
|
|
||||||
Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](contributing.md)'na göz atın
|
Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](../contributing/contributing.md)'na göz atın
|
||||||
|
|
||||||
Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin.
|
Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin.
|
||||||
|
|
@ -10,8 +10,6 @@
|
|||||||
[![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) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md)
|
|
||||||
|
|
||||||
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
|
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
|
||||||
|
|
||||||
Bruno зберігає ваші колекції напряму у теці на вашому диску. Він використовує текстову мову розмітки Bru для збереження інформації про ваші API запити.
|
Bruno зберігає ваші колекції напряму у теці на вашому диску. Він використовує текстову мову розмітки Bru для збереження інформації про ваші API запити.
|
||||||
@ -56,7 +54,7 @@ Bruno є повністю автономним. Немає жодних план
|
|||||||
|
|
||||||
### Зробити свій внесок 👩💻🧑💻
|
### Зробити свій внесок 👩💻🧑💻
|
||||||
|
|
||||||
Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing_ua.md)
|
Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](../contributing/contributing_ua.md)
|
||||||
|
|
||||||
Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі.
|
Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі.
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -16630,7 +16630,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v0.27.1",
|
"version": "v0.27.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "^3.425.0",
|
"@aws-sdk/credential-providers": "^3.425.0",
|
||||||
"@usebruno/js": "0.9.1",
|
"@usebruno/js": "0.9.1",
|
||||||
@ -16641,6 +16641,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"content-disposition": "^0.5.4",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
@ -16656,6 +16657,7 @@
|
|||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
@ -21519,6 +21521,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"content-disposition": "^0.5.4",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dmg-license": "^1.0.11",
|
"dmg-license": "^1.0.11",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
@ -21538,6 +21541,7 @@
|
|||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENV=dev next dev",
|
"dev": "cross-env ENV=dev next dev -p 3000",
|
||||||
"build": "next build && next export",
|
"build": "next build && next export",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
@ -70,6 +70,35 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Ctrl-F': 'findPersistent',
|
'Ctrl-F': 'findPersistent',
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
cm.replaceSelection(' ', 'end');
|
cm.replaceSelection(' ', 'end');
|
||||||
|
},
|
||||||
|
'Ctrl-Y': 'foldAll',
|
||||||
|
'Cmd-Y': 'foldAll',
|
||||||
|
'Ctrl-I': 'unfoldAll',
|
||||||
|
'Cmd-I': 'unfoldAll'
|
||||||
|
},
|
||||||
|
foldOptions: {
|
||||||
|
widget: (from, to) => {
|
||||||
|
var count = undefined;
|
||||||
|
var internal = this.editor.getRange(from, to);
|
||||||
|
if (this.props.mode == 'application/ld+json') {
|
||||||
|
if (this.editor.getLine(from.line).endsWith('[')) {
|
||||||
|
var toParse = '[' + internal + ']';
|
||||||
|
} else var toParse = '{' + internal + '}';
|
||||||
|
try {
|
||||||
|
count = Object.keys(JSON.parse(toParse)).length;
|
||||||
|
} catch (e) {}
|
||||||
|
} else if (this.props.mode == 'application/xml') {
|
||||||
|
var doc = new DOMParser();
|
||||||
|
try {
|
||||||
|
//add header element and remove prefix namespaces for DOMParser
|
||||||
|
var dcm = doc.parseFromString(
|
||||||
|
'<a> ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '</a>',
|
||||||
|
'application/xml'
|
||||||
|
);
|
||||||
|
count = dcm.documentElement.children.length;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return count ? `\u21A4${count}\u21A6` : '\u2194';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -13,10 +13,12 @@ const Wrapper = styled.div`
|
|||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
|
|
||||||
&:nth-child(1),
|
&:nth-child(1),
|
||||||
&:nth-child(4),
|
&:nth-child(4) {
|
||||||
&:nth-child(5) {
|
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
&:nth-child(5) {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
@ -1,68 +1,79 @@
|
|||||||
import React, { useReducer } from 'react';
|
import React from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { IconTrash } from '@tabler/icons';
|
import { IconTrash } from '@tabler/icons';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import reducer from './reducer';
|
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
|
||||||
const EnvironmentVariables = ({ environment, collection }) => {
|
const EnvironmentVariables = ({ environment, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
|
|
||||||
const { variables, hasChanges } = state;
|
|
||||||
|
|
||||||
const saveChanges = () => {
|
const formik = useFormik({
|
||||||
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
|
enableReinitialize: true,
|
||||||
|
initialValues: environment.variables || [],
|
||||||
|
validationSchema: Yup.array().of(
|
||||||
|
Yup.object({
|
||||||
|
enabled: Yup.boolean(),
|
||||||
|
name: Yup.string()
|
||||||
|
.required('Name cannot be empty')
|
||||||
|
.matches(/^(?!\d)\w*$/, 'Name contains invalid characters')
|
||||||
|
.trim(),
|
||||||
|
secret: Yup.boolean(),
|
||||||
|
type: Yup.string(),
|
||||||
|
uid: Yup.string(),
|
||||||
|
value: Yup.string().trim()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (!formik.dirty) {
|
||||||
|
toast.error('Nothing to save');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(saveEnvironment(cloneDeep(values), environment.uid, collection.uid))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success('Changes saved successfully');
|
toast.success('Changes saved successfully');
|
||||||
reducerDispatch({
|
formik.resetForm({ values });
|
||||||
type: 'CHANGES_SAVED'
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(() => toast.error('An error occurred while saving the changes'));
|
.catch(() => toast.error('An error occurred while saving the changes'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ErrorMessage = ({ name }) => {
|
||||||
|
const meta = formik.getFieldMeta(name);
|
||||||
|
console.log(name, meta);
|
||||||
|
if (!meta.error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label htmlFor={name} className="text-red-500">
|
||||||
|
{meta.error}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addVariable = () => {
|
const addVariable = () => {
|
||||||
reducerDispatch({
|
const newVariable = {
|
||||||
type: 'ADD_VAR'
|
uid: uuid(),
|
||||||
});
|
name: '',
|
||||||
|
value: '',
|
||||||
|
type: 'text',
|
||||||
|
secret: false,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVarChange = (e, _variable, type) => {
|
const handleRemoveVar = (id) => {
|
||||||
const variable = cloneDeep(_variable);
|
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||||
switch (type) {
|
|
||||||
case 'name': {
|
|
||||||
variable.name = e.target.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'value': {
|
|
||||||
variable.value = e.target.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'enabled': {
|
|
||||||
variable.enabled = e.target.checked;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'secret': {
|
|
||||||
variable.secret = e.target.checked;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reducerDispatch({
|
|
||||||
type: 'UPDATE_VAR',
|
|
||||||
variable
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveVars = (variable) => {
|
|
||||||
reducerDispatch({
|
|
||||||
type: 'DELETE_VAR',
|
|
||||||
variable
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -78,16 +89,15 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{variables && variables.length
|
{formik.values.map((variable, index) => (
|
||||||
? variables.map((variable, index) => {
|
|
||||||
return (
|
|
||||||
<tr key={variable.uid}>
|
<tr key={variable.uid}>
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={variable.enabled}
|
|
||||||
className="mr-3 mousetrap"
|
className="mr-3 mousetrap"
|
||||||
onChange={(e) => handleVarChange(e, variable, 'enabled')}
|
name={`${index}.enabled`}
|
||||||
|
checked={variable.enabled}
|
||||||
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -97,36 +107,39 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
|||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
value={variable.name}
|
|
||||||
className="mousetrap"
|
className="mousetrap"
|
||||||
onChange={(e) => handleVarChange(e, variable, 'name')}
|
id={`${index}.name`}
|
||||||
|
name={`${index}.name`}
|
||||||
|
value={formik.values[index].name}
|
||||||
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
|
<ErrorMessage name={`${index}.name`} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<SingleLineEditor
|
<SingleLineEditor
|
||||||
value={variable.value}
|
|
||||||
theme={storedTheme}
|
theme={storedTheme}
|
||||||
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
|
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
name={`${index}.value`}
|
||||||
|
value={variable.value}
|
||||||
|
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={variable.secret}
|
|
||||||
className="mr-3 mousetrap"
|
className="mr-3 mousetrap"
|
||||||
onChange={(e) => handleVarChange(e, variable, 'secret')}
|
name={`${index}.secret`}
|
||||||
|
checked={variable.secret}
|
||||||
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button onClick={() => handleRemoveVars(variable)}>
|
<button onClick={() => handleRemoveVar(variable.uid)}>
|
||||||
<IconTrash strokeWidth={1.5} size={20} />
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
))}
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@ -137,12 +150,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||||
type="submit"
|
|
||||||
className="submit btn btn-md btn-secondary mt-2"
|
|
||||||
disabled={!hasChanges}
|
|
||||||
onClick={saveChanges}
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
import produce from 'immer';
|
|
||||||
import find from 'lodash/find';
|
|
||||||
import filter from 'lodash/filter';
|
|
||||||
import { uuid } from 'utils/common';
|
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'ADD_VAR': {
|
|
||||||
return produce(state, (draft) => {
|
|
||||||
draft.variables.push({
|
|
||||||
uid: uuid(),
|
|
||||||
name: '',
|
|
||||||
value: '',
|
|
||||||
type: 'text',
|
|
||||||
secret: false,
|
|
||||||
enabled: true
|
|
||||||
});
|
|
||||||
draft.hasChanges = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'UPDATE_VAR': {
|
|
||||||
return produce(state, (draft) => {
|
|
||||||
const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
|
|
||||||
variable.name = action.variable.name;
|
|
||||||
variable.value = action.variable.value;
|
|
||||||
variable.enabled = action.variable.enabled;
|
|
||||||
variable.secret = action.variable.secret;
|
|
||||||
draft.hasChanges = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'DELETE_VAR': {
|
|
||||||
return produce(state, (draft) => {
|
|
||||||
draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid);
|
|
||||||
draft.hasChanges = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'CHANGES_SAVED': {
|
|
||||||
return produce(state, (draft) => {
|
|
||||||
draft.hasChanges = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default reducer;
|
|
@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
|
|||||||
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||||
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
|
height: 100%;
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@ -44,12 +45,10 @@ const StyledWrapper = styled.div`
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
color: ${(props) => props.theme.textLink};
|
color: ${(props) => props.theme.textLink};
|
||||||
|
|
||||||
&:hover {
|
span:hover {
|
||||||
span {
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.btn-import-environment {
|
.btn-import-environment {
|
||||||
color: ${(props) => props.theme.colors.text.muted};
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { useEffect, useState, forwardRef, useRef } from 'react';
|
import React, { useEffect, useState, forwardRef, useRef } from 'react';
|
||||||
import { findEnvironmentInCollection } from 'utils/collections';
|
import { findEnvironmentInCollection } from 'utils/collections';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { toastError } from 'utils/common/error';
|
|
||||||
import usePrevious from 'hooks/usePrevious';
|
import usePrevious from 'hooks/usePrevious';
|
||||||
import EnvironmentDetails from './EnvironmentDetails';
|
import EnvironmentDetails from './EnvironmentDetails';
|
||||||
import CreateEnvironment from '../CreateEnvironment';
|
import CreateEnvironment from '../CreateEnvironment';
|
||||||
import { IconDownload } from '@tabler/icons';
|
import { IconDownload, IconShieldLock } from '@tabler/icons';
|
||||||
import ImportEnvironment from '../ImportEnvironment';
|
import ImportEnvironment from '../ImportEnvironment';
|
||||||
|
import ManageSecrets from '../ManageSecrets';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const EnvironmentList = ({ collection }) => {
|
const EnvironmentList = ({ collection }) => {
|
||||||
@ -14,6 +13,7 @@ const EnvironmentList = ({ collection }) => {
|
|||||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||||
const [openImportModal, setOpenImportModal] = useState(false);
|
const [openImportModal, setOpenImportModal] = useState(false);
|
||||||
|
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
|
||||||
|
|
||||||
const envUids = environments ? environments.map((env) => env.uid) : [];
|
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||||
const prevEnvUids = usePrevious(envUids);
|
const prevEnvUids = usePrevious(envUids);
|
||||||
@ -54,6 +54,7 @@ const EnvironmentList = ({ collection }) => {
|
|||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
||||||
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
|
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
|
||||||
|
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div>
|
<div>
|
||||||
<div className="environments-sidebar flex flex-col">
|
<div className="environments-sidebar flex flex-col">
|
||||||
@ -72,10 +73,16 @@ const EnvironmentList = ({ collection }) => {
|
|||||||
+ <span>Create</span>
|
+ <span>Create</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-auto flex items-center btn-import-environment" onClick={() => setOpenImportModal(true)}>
|
<div className="mt-auto btn-import-environment">
|
||||||
|
<div className="flex items-center" onClick={() => setOpenImportModal(true)}>
|
||||||
<IconDownload size={12} strokeWidth={2} />
|
<IconDownload size={12} strokeWidth={2} />
|
||||||
<span className="label ml-1 text-xs">Import</span>
|
<span className="label ml-1 text-xs">Import</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center mt-2" onClick={() => setOpenManageSecretsModal(true)}>
|
||||||
|
<IconShieldLock size={12} strokeWidth={2} />
|
||||||
|
<span className="label ml-1 text-xs">Managing Secrets</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentDetails environment={selectedEnvironment} collection={collection} />
|
<EnvironmentDetails environment={selectedEnvironment} collection={collection} />
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
|
||||||
|
const ManageSecrets = ({ onClose }) => {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal size="sm" title="Manage Secrets" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||||
|
<div>
|
||||||
|
<p>In any collection, there are secrets that need to be managed.</p>
|
||||||
|
<p className="mt-2">These secrets can be anything such as API keys, passwords, or tokens.</p>
|
||||||
|
<p className="mt-4">Bruno offers two approaches to manage secrets in collections.</p>
|
||||||
|
<p className="mt-2">
|
||||||
|
Read more about it in our{' '}
|
||||||
|
<a
|
||||||
|
href="https://docs.usebruno.com/secrets-management/overview.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-link hover:underline"
|
||||||
|
>
|
||||||
|
docs
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageSecrets;
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons';
|
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import QueryEditor from 'components/RequestPane/QueryEditor';
|
import QueryEditor from 'components/RequestPane/QueryEditor';
|
||||||
@ -16,10 +15,9 @@ import Tests from 'components/RequestPane/Tests';
|
|||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
|
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { findEnvironmentInCollection } from 'utils/collections';
|
|
||||||
import useGraphqlSchema from './useGraphqlSchema';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import Documentation from 'components/Documentation/index';
|
import Documentation from 'components/Documentation/index';
|
||||||
|
import GraphQLSchemaActions from '../GraphQLSchemaActions/index';
|
||||||
|
|
||||||
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
|
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -29,25 +27,11 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
const variables = item.draft
|
const variables = item.draft
|
||||||
? get(item, 'draft.request.body.graphql.variables')
|
? get(item, 'draft.request.body.graphql.variables')
|
||||||
: get(item, 'request.body.graphql.variables');
|
: get(item, 'request.body.graphql.variables');
|
||||||
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
|
const [schema, setSchema] = useState(null);
|
||||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
|
||||||
|
|
||||||
const request = item.draft ? item.draft.request : item.request;
|
|
||||||
|
|
||||||
let { schema, loadSchema, isLoading: isSchemaLoading } = useGraphqlSchema(url, environment, request, collection);
|
|
||||||
|
|
||||||
const loadGqlSchema = () => {
|
|
||||||
if (!isSchemaLoading) {
|
|
||||||
loadSchema();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onSchemaLoad) {
|
|
||||||
onSchemaLoad(schema);
|
onSchemaLoad(schema);
|
||||||
}
|
|
||||||
}, [schema]);
|
}, [schema]);
|
||||||
|
|
||||||
const onQueryChange = (value) => {
|
const onQueryChange = (value) => {
|
||||||
@ -163,18 +147,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
|
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
|
||||||
Docs
|
Docs
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
|
<GraphQLSchemaActions item={item} collection={collection} onSchemaLoad={setSchema} toggleDocs={toggleDocs} />
|
||||||
<div className="flex items-center cursor-pointer hover:underline" onClick={loadGqlSchema}>
|
|
||||||
{isSchemaLoading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
|
|
||||||
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5} /> : null}
|
|
||||||
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5} /> : null}
|
|
||||||
<span className="ml-1">Schema</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center cursor-pointer hover:underline ml-2" onClick={toggleDocs}>
|
|
||||||
<IconBook size={18} strokeWidth={1.5} />
|
|
||||||
<span className="ml-1">Docs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
import { buildClientSchema } from 'graphql';
|
|
||||||
import { fetchGqlSchema } from 'utils/network';
|
|
||||||
import { simpleHash } from 'utils/common';
|
|
||||||
|
|
||||||
const schemaHashPrefix = 'bruno.graphqlSchema';
|
|
||||||
|
|
||||||
const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
|
||||||
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [schema, setSchema] = useState(() => {
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem(localStorageKey);
|
|
||||||
if (!saved) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return buildClientSchema(JSON.parse(saved));
|
|
||||||
} catch {
|
|
||||||
localStorage.setItem(localStorageKey, null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadSchema = () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
fetchGqlSchema(endpoint, environment, request, collection)
|
|
||||||
.then((res) => {
|
|
||||||
if (!res || res.status !== 200) {
|
|
||||||
return Promise.reject(new Error(res.statusText));
|
|
||||||
}
|
|
||||||
return res.data;
|
|
||||||
})
|
|
||||||
.then((s) => {
|
|
||||||
if (s && s.data) {
|
|
||||||
setSchema(buildClientSchema(s.data));
|
|
||||||
setIsLoading(false);
|
|
||||||
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
|
|
||||||
toast.success('GraphQL Schema loaded successfully');
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error('An error occurred while introspecting schema'));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setError(err);
|
|
||||||
toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
schema,
|
|
||||||
loadSchema,
|
|
||||||
error
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useGraphqlSchema;
|
|
@ -0,0 +1,70 @@
|
|||||||
|
import React, { useEffect, useRef, forwardRef } from 'react';
|
||||||
|
import useGraphqlSchema from './useGraphqlSchema';
|
||||||
|
import { IconBook, IconDownload, IconLoader2, IconCheckmark } from '@tabler/icons';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { findEnvironmentInCollection } from 'utils/collections';
|
||||||
|
import Dropdown from '../../Dropdown';
|
||||||
|
|
||||||
|
const GraphQLSchemaActions = ({ item, collection, onSchemaLoad, toggleDocs }) => {
|
||||||
|
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||||
|
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||||
|
const request = item.draft ? item.draft.request : item.request;
|
||||||
|
|
||||||
|
let {
|
||||||
|
schema,
|
||||||
|
schemaSource,
|
||||||
|
loadSchema,
|
||||||
|
isLoading: isSchemaLoading
|
||||||
|
} = useGraphqlSchema(url, environment, request, collection);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onSchemaLoad) {
|
||||||
|
onSchemaLoad(schema);
|
||||||
|
}
|
||||||
|
}, [schema]);
|
||||||
|
|
||||||
|
const schemaDropdownTippyRef = useRef();
|
||||||
|
const onSchemaDropdownCreate = (ref) => (schemaDropdownTippyRef.current = ref);
|
||||||
|
|
||||||
|
const MenuIcon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="dropdown-icon cursor-pointer flex hover:underline ml-2">
|
||||||
|
{isSchemaLoading && <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />}
|
||||||
|
{!isSchemaLoading && schema && <IconDownload size={18} strokeWidth={1.5} />}
|
||||||
|
{!isSchemaLoading && !schema && <IconCheckmark size={18} strokeWidth={1.5} />}
|
||||||
|
<span className="ml-1">Schema</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
|
||||||
|
<div className="flex items-center cursor-pointer hover:underline" onClick={toggleDocs}>
|
||||||
|
<IconBook size={18} strokeWidth={1.5} />
|
||||||
|
<span className="ml-1">Docs</span>
|
||||||
|
</div>
|
||||||
|
<Dropdown onCreate={onSchemaDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
schemaDropdownTippyRef.current.hide();
|
||||||
|
loadSchema('introspection');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{schema && schemaSource === 'introspection' ? 'Refresh from Introspection' : 'Load from Introspection'}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
schemaDropdownTippyRef.current.hide();
|
||||||
|
loadSchema('file');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Load from File
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GraphQLSchemaActions;
|
@ -0,0 +1,89 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { buildClientSchema } from 'graphql';
|
||||||
|
import { fetchGqlSchema } from 'utils/network';
|
||||||
|
import { simpleHash } from 'utils/common';
|
||||||
|
|
||||||
|
const schemaHashPrefix = 'bruno.graphqlSchema';
|
||||||
|
|
||||||
|
const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [schemaSource, setSchemaSource] = useState('');
|
||||||
|
const [schema, setSchema] = useState(() => {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(localStorageKey);
|
||||||
|
if (!saved) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return buildClientSchema(JSON.parse(saved));
|
||||||
|
} catch {
|
||||||
|
localStorage.setItem(localStorageKey, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadSchemaFromIntrospection = async () => {
|
||||||
|
const response = await fetchGqlSchema(endpoint, environment, request, collection);
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Introspection query failed');
|
||||||
|
}
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
const data = response.data?.data;
|
||||||
|
if (!data) {
|
||||||
|
throw new Error('No data returned from introspection query');
|
||||||
|
}
|
||||||
|
setSchemaSource('introspection');
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadSchemaFromFile = async () => {
|
||||||
|
const schemaContent = await ipcRenderer.invoke('renderer:load-gql-schema-file');
|
||||||
|
if (!schemaContent) {
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSchemaSource('file');
|
||||||
|
return schemaContent.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadSchema = async (schemaSource) => {
|
||||||
|
if (isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data;
|
||||||
|
if (schemaSource === 'file') {
|
||||||
|
data = await loadSchemaFromFile();
|
||||||
|
} else {
|
||||||
|
// fallback to introspection if source is unknown
|
||||||
|
data = await loadSchemaFromIntrospection();
|
||||||
|
}
|
||||||
|
setSchema(buildClientSchema(data));
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(data));
|
||||||
|
toast.success('GraphQL Schema loaded successfully');
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
schema,
|
||||||
|
schemaSource,
|
||||||
|
loadSchema,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGraphqlSchema;
|
@ -8,6 +8,7 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection
|
|||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import Tooltip from 'components/Tooltip';
|
import Tooltip from 'components/Tooltip';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
const VarsTable = ({ item, collection, vars, varType }) => {
|
const VarsTable = ({ item, collection, vars, varType }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -29,7 +30,19 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
|||||||
const _var = cloneDeep(v);
|
const _var = cloneDeep(v);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'name': {
|
case 'name': {
|
||||||
_var.name = e.target.value;
|
const value = e.target.value;
|
||||||
|
|
||||||
|
if (/^(?!\d).*$/.test(value) === false) {
|
||||||
|
toast.error('Variable names must not start with a number!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\w*$/.test(value) === false) {
|
||||||
|
toast.error('Variable contains invalid character! Variables must only contain alpha-numeric characters.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_var.name = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'value': {
|
case 'value': {
|
||||||
@ -88,7 +101,7 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{vars && vars.length
|
{vars && vars.length
|
||||||
? vars.map((_var, index) => {
|
? vars.map((_var) => {
|
||||||
return (
|
return (
|
||||||
<tr key={_var.uid}>
|
<tr key={_var.uid}>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,42 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
|
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
|
||||||
import { useTheme } from 'providers/Theme';
|
|
||||||
import VarsTable from './VarsTable';
|
import VarsTable from './VarsTable';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const Vars = ({ item, collection }) => {
|
const Vars = ({ item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
|
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
|
||||||
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
|
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
|
||||||
|
|
||||||
const { storedTheme } = useTheme();
|
|
||||||
|
|
||||||
const onRequestScriptEdit = (value) => {
|
|
||||||
dispatch(
|
|
||||||
updateRequestScript({
|
|
||||||
script: value,
|
|
||||||
itemUid: item.uid,
|
|
||||||
collectionUid: collection.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onResponseScriptEdit = (value) => {
|
|
||||||
dispatch(
|
|
||||||
updateResponseScript({
|
|
||||||
script: value,
|
|
||||||
itemUid: item.uid,
|
|
||||||
collectionUid: collection.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
|
||||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full flex flex-col">
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
<div className="flex-1 mt-2">
|
<div className="flex-1 mt-2">
|
||||||
|
@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
|
|||||||
color: ${(props) => props.theme.colors.text.yellow};
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -85,7 +85,16 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
|||||||
{tabs}
|
{tabs}
|
||||||
</div>
|
</div>
|
||||||
{error ? (
|
{error ? (
|
||||||
<span className="text-red-500">{error}</span>
|
<div>
|
||||||
|
<div className="text-red-500">{error}</div>
|
||||||
|
|
||||||
|
{error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? (
|
||||||
|
<div className="mt-6 muted text-xs">
|
||||||
|
You can disable SSL verification in the Preferences. <br />
|
||||||
|
To open the Preferences, click on the gear icon in the bottom left corner.
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<QueryResultPreview
|
<QueryResultPreview
|
||||||
previewTab={previewTab}
|
previewTab={previewTab}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: ${(props) => props.theme.requestTabPanel.responseStatus};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { IconDownload } from '@tabler/icons';
|
||||||
|
|
||||||
|
const ResponseSave = ({ item }) => {
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
const response = item.response || {};
|
||||||
|
|
||||||
|
const saveResponseToFile = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(item);
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-response-to-file', response, item.requestSent.url)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(get(err, 'error.message') || 'Something went wrong!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="ml-4 flex items-center">
|
||||||
|
<button onClick={saveResponseToFile} disabled={!response.dataBuffer} title="Save response to file">
|
||||||
|
<IconDownload size={16} strokeWidth={1.5} />
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ResponseSave;
|
@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper';
|
|||||||
// Todo: text-error class is not getting pulled in for 500 errors
|
// Todo: text-error class is not getting pulled in for 500 errors
|
||||||
const StatusCode = ({ status }) => {
|
const StatusCode = ({ status }) => {
|
||||||
const getTabClassname = (status) => {
|
const getTabClassname = (status) => {
|
||||||
return classnames('', {
|
return classnames('ml-2', {
|
||||||
'text-ok': status >= 100 && status < 200,
|
'text-ok': status >= 100 && status < 200,
|
||||||
'text-ok': status >= 200 && status < 300,
|
'text-ok': status >= 200 && status < 300,
|
||||||
'text-error': status >= 300 && status < 400,
|
'text-error': status >= 300 && status < 400,
|
||||||
|
@ -14,6 +14,7 @@ import Timeline from './Timeline';
|
|||||||
import TestResults from './TestResults';
|
import TestResults from './TestResults';
|
||||||
import TestResultsLabel from './TestResultsLabel';
|
import TestResultsLabel from './TestResultsLabel';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
|
||||||
|
|
||||||
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -112,6 +113,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
|||||||
</div>
|
</div>
|
||||||
{!isLoading ? (
|
{!isLoading ? (
|
||||||
<div className="flex flex-grow justify-end items-center">
|
<div className="flex flex-grow justify-end items-center">
|
||||||
|
<ResponseSave item={item} />
|
||||||
<StatusCode status={response.status} />
|
<StatusCode status={response.status} />
|
||||||
<ResponseTime duration={response.duration} />
|
<ResponseTime duration={response.duration} />
|
||||||
<ResponseSize size={response.size} />
|
<ResponseSize size={response.size} />
|
||||||
|
@ -7,7 +7,7 @@ import Preferences from 'components/Preferences';
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { IconSettings } from '@tabler/icons';
|
import { IconSettings } from '@tabler/icons';
|
||||||
import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app';
|
import { updateLeftSidebarWidth, updateIsDragging, showPreferences } from 'providers/ReduxStore/slices/app';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
|
|
||||||
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
||||||
@ -15,7 +15,7 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600;
|
|||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||||
const [preferencesOpen, setPreferencesOpen] = useState(false);
|
const preferencesOpen = useSelector((state) => state.app.showPreferences);
|
||||||
|
|
||||||
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ const Sidebar = () => {
|
|||||||
<StyledWrapper className="flex relative h-screen">
|
<StyledWrapper className="flex relative h-screen">
|
||||||
<aside>
|
<aside>
|
||||||
<div className="flex flex-row h-screen w-full">
|
<div className="flex flex-row h-screen w-full">
|
||||||
{preferencesOpen && <Preferences onClose={() => setPreferencesOpen(false)} />}
|
{preferencesOpen && <Preferences onClose={() => dispatch(showPreferences(false))} />}
|
||||||
|
|
||||||
<div className="flex flex-col w-full" style={{ width: asideWidth }}>
|
<div className="flex flex-col w-full" style={{ width: asideWidth }}>
|
||||||
<div className="flex flex-col flex-grow">
|
<div className="flex flex-col flex-grow">
|
||||||
@ -92,7 +92,7 @@ const Sidebar = () => {
|
|||||||
size={18}
|
size={18}
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
className="mr-2 hover:text-gray-700"
|
className="mr-2 hover:text-gray-700"
|
||||||
onClick={() => setPreferencesOpen(true)}
|
onClick={() => dispatch(showPreferences(true))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
|
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
|
||||||
|
@ -21,6 +21,7 @@ if (!SERVER_RENDERED) {
|
|||||||
require('codemirror/addon/edit/matchbrackets');
|
require('codemirror/addon/edit/matchbrackets');
|
||||||
require('codemirror/addon/fold/brace-fold');
|
require('codemirror/addon/fold/brace-fold');
|
||||||
require('codemirror/addon/fold/foldgutter');
|
require('codemirror/addon/fold/foldgutter');
|
||||||
|
require('codemirror/addon/fold/xml-fold');
|
||||||
require('codemirror/addon/hint/show-hint');
|
require('codemirror/addon/hint/show-hint');
|
||||||
require('codemirror/addon/lint/lint');
|
require('codemirror/addon/lint/lint');
|
||||||
require('codemirror/addon/mode/overlay');
|
require('codemirror/addon/mode/overlay');
|
||||||
|
@ -41,6 +41,19 @@ function MyApp({ Component, pageProps }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.ipcRenderer) {
|
||||||
|
return (
|
||||||
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mx-10 my-10 rounded relative" role="alert">
|
||||||
|
<strong class="font-bold">ERROR:</strong>
|
||||||
|
<span className="block inline ml-1">"ipcRenderer" not found in window object.</span>
|
||||||
|
<div>
|
||||||
|
You most likely opened Bruno inside your web browser. Bruno only works within Electron, you can start Electron
|
||||||
|
in an adjacent terminal using "npm run dev:electron".
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<SafeHydrate>
|
<SafeHydrate>
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
runFolderEvent,
|
runFolderEvent,
|
||||||
brunoConfigUpdateEvent
|
brunoConfigUpdateEvent
|
||||||
} from 'providers/ReduxStore/slices/collections';
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
import { updatePreferences } from 'providers/ReduxStore/slices/app';
|
import { showPreferences, updatePreferences } from 'providers/ReduxStore/slices/app';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
|
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { isElectron } from 'utils/common/platform';
|
import { isElectron } from 'utils/common/platform';
|
||||||
@ -127,6 +127,10 @@ const useIpcEvents = () => {
|
|||||||
dispatch(brunoConfigUpdateEvent(val))
|
dispatch(brunoConfigUpdateEvent(val))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showPreferencesListener = ipcRenderer.on('main:open-preferences', () => {
|
||||||
|
dispatch(showPreferences(true));
|
||||||
|
});
|
||||||
|
|
||||||
const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => {
|
const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => {
|
||||||
dispatch(updatePreferences(val));
|
dispatch(updatePreferences(val));
|
||||||
});
|
});
|
||||||
@ -143,6 +147,7 @@ const useIpcEvents = () => {
|
|||||||
removeProcessEnvUpdatesListener();
|
removeProcessEnvUpdatesListener();
|
||||||
removeConsoleLogListener();
|
removeConsoleLogListener();
|
||||||
removeConfigUpdatesListener();
|
removeConfigUpdatesListener();
|
||||||
|
showPreferencesListener();
|
||||||
removePreferencesUpdatesListener();
|
removePreferencesUpdatesListener();
|
||||||
};
|
};
|
||||||
}, [isElectron]);
|
}, [isElectron]);
|
||||||
|
@ -7,6 +7,7 @@ const initialState = {
|
|||||||
leftSidebarWidth: 222,
|
leftSidebarWidth: 222,
|
||||||
screenWidth: 500,
|
screenWidth: 500,
|
||||||
showHomePage: false,
|
showHomePage: false,
|
||||||
|
showPreferences: false,
|
||||||
preferences: {
|
preferences: {
|
||||||
request: {
|
request: {
|
||||||
sslVerification: true,
|
sslVerification: true,
|
||||||
@ -40,6 +41,9 @@ export const appSlice = createSlice({
|
|||||||
hideHomePage: (state) => {
|
hideHomePage: (state) => {
|
||||||
state.showHomePage = false;
|
state.showHomePage = false;
|
||||||
},
|
},
|
||||||
|
showPreferences: (state, action) => {
|
||||||
|
state.showPreferences = action.payload;
|
||||||
|
},
|
||||||
updatePreferences: (state, action) => {
|
updatePreferences: (state, action) => {
|
||||||
state.preferences = action.payload;
|
state.preferences = action.payload;
|
||||||
}
|
}
|
||||||
@ -53,6 +57,7 @@ export const {
|
|||||||
updateIsDragging,
|
updateIsDragging,
|
||||||
showHomePage,
|
showHomePage,
|
||||||
hideHomePage,
|
hideHomePage,
|
||||||
|
showPreferences,
|
||||||
updatePreferences
|
updatePreferences
|
||||||
} = appSlice.actions;
|
} = appSlice.actions;
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
if (!brunoRequestItem.request.script) {
|
if (!brunoRequestItem.request.script) {
|
||||||
brunoRequestItem.request.script = {};
|
brunoRequestItem.request.script = {};
|
||||||
}
|
}
|
||||||
if (Array.isArray(event.script.exec[0])) {
|
if (Array.isArray(event.script.exec)) {
|
||||||
brunoRequestItem.request.script.req = event.script.exec[0].map((line) => `// ${line}`).join('\n');
|
brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n');
|
||||||
} else {
|
} else {
|
||||||
brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `;
|
brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `;
|
||||||
}
|
}
|
||||||
@ -78,8 +78,8 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
if (!brunoRequestItem.request.tests) {
|
if (!brunoRequestItem.request.tests) {
|
||||||
brunoRequestItem.request.tests = {};
|
brunoRequestItem.request.tests = {};
|
||||||
}
|
}
|
||||||
if (Array.isArray(event.script.exec[0])) {
|
if (Array.isArray(event.script.exec)) {
|
||||||
brunoRequestItem.request.tests = event.script.exec[0].map((line) => `// ${line}`).join('\n');
|
brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n');
|
||||||
} else {
|
} else {
|
||||||
brunoRequestItem.request.tests = `// ${event.script.exec[0]} `;
|
brunoRequestItem.request.tests = `// ${event.script.exec[0]} `;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
"content-disposition": "^0.5.4",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
@ -43,6 +44,7 @@
|
|||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
|
@ -12,6 +12,14 @@ const template = [
|
|||||||
ipcMain.emit('main:open-collection');
|
ipcMain.emit('main:open-collection');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Preferences',
|
||||||
|
accelerator: 'CommandOrControl+,',
|
||||||
|
click() {
|
||||||
|
ipcMain.emit('main:open-preferences');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{ role: 'quit' }
|
{ role: 'quit' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -67,7 +67,21 @@ app.on('ready', async () => {
|
|||||||
slashes: true
|
slashes: true
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.loadURL(url);
|
mainWindow.loadURL(url).catch((reason) => {
|
||||||
|
console.error(`Error: Failed to load URL: "${url}" (Electron shows a blank screen because of this).`);
|
||||||
|
console.error('Original message:', reason);
|
||||||
|
if (isDev) {
|
||||||
|
console.error(
|
||||||
|
'Could not connect to Next.Js dev server, is it running?' +
|
||||||
|
' Start the dev server using "npm run dev:web" and restart electron'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
'If you are using an official production build: the above error is most likely a bug! ' +
|
||||||
|
' Please report this under: https://github.com/usebruno/bruno/issues'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
watcher = new Watcher();
|
watcher = new Watcher();
|
||||||
|
|
||||||
const handleBoundsChange = () => {
|
const handleBoundsChange = () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { ipcMain, shell } = require('electron');
|
const { ipcMain, shell, dialog } = require('electron');
|
||||||
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -461,6 +461,22 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
ipcMain.handle('renderer:open-devtools', async () => {
|
ipcMain.handle('renderer:open-devtools', async () => {
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:load-gql-schema-file', async () => {
|
||||||
|
try {
|
||||||
|
const { filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
properties: ['openFile']
|
||||||
|
});
|
||||||
|
if (filePaths.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData = fs.readFileSync(filePaths[0], 'utf8');
|
||||||
|
return JSON.parse(jsonData);
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(new Error('Failed to load GraphQL schema file'));
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
|
@ -3,9 +3,12 @@ const fs = require('fs');
|
|||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const path = require('path');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
|
const contentDispositionParser = require('content-disposition');
|
||||||
|
const mime = require('mime-types');
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const { forOwn, extend, each, get, compact } = require('lodash');
|
const { forOwn, extend, each, get, compact } = require('lodash');
|
||||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
@ -24,6 +27,7 @@ const { SocksProxyAgent } = require('socks-proxy-agent');
|
|||||||
const { makeAxiosInstance } = require('./axios-instance');
|
const { makeAxiosInstance } = require('./axios-instance');
|
||||||
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
||||||
|
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
|
||||||
|
|
||||||
// override the default escape function to prevent escaping
|
// override the default escape function to prevent escaping
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
@ -83,7 +87,14 @@ const getSize = (data) => {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => {
|
const configureRequest = async (
|
||||||
|
collectionUid,
|
||||||
|
request,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
processEnvVars,
|
||||||
|
collectionPath
|
||||||
|
) => {
|
||||||
const httpsAgentRequestFields = {};
|
const httpsAgentRequestFields = {};
|
||||||
if (!preferencesUtil.shouldVerifyTls()) {
|
if (!preferencesUtil.shouldVerifyTls()) {
|
||||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||||
@ -100,18 +111,29 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
|
|||||||
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
|
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
|
||||||
for (let clientCert of clientCertConfig) {
|
for (let clientCert of clientCertConfig) {
|
||||||
const domain = interpolateString(clientCert.domain, interpolationOptions);
|
const domain = interpolateString(clientCert.domain, interpolationOptions);
|
||||||
const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
|
|
||||||
const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
|
let certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
|
||||||
|
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
|
||||||
|
|
||||||
|
let keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
|
||||||
|
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
|
||||||
|
|
||||||
if (domain && certFilePath && keyFilePath) {
|
if (domain && certFilePath && keyFilePath) {
|
||||||
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
|
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
|
||||||
|
|
||||||
if (request.url.match(hostRegex)) {
|
if (request.url.match(hostRegex)) {
|
||||||
try {
|
try {
|
||||||
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
|
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error reading cert file', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
|
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error reading cert/key file', err);
|
console.log('Error reading key file', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
|
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -394,7 +416,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
request,
|
request,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
collectionPath
|
||||||
);
|
);
|
||||||
|
|
||||||
let response, responseTime;
|
let response, responseTime;
|
||||||
@ -569,7 +592,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
preparedRequest,
|
preparedRequest,
|
||||||
envVars,
|
envVars,
|
||||||
collection.collectionVariables,
|
collection.collectionVariables,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
collectionPath
|
||||||
);
|
);
|
||||||
const response = await axiosInstance(preparedRequest);
|
const response = await axiosInstance(preparedRequest);
|
||||||
|
|
||||||
@ -707,7 +731,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
request,
|
request,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
collectionPath
|
||||||
);
|
);
|
||||||
|
|
||||||
timeStart = Date.now();
|
timeStart = Date.now();
|
||||||
@ -865,6 +890,51 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// save response to file
|
||||||
|
ipcMain.handle('renderer:save-response-to-file', async (event, response, url) => {
|
||||||
|
try {
|
||||||
|
const getHeaderValue = (headerName) => {
|
||||||
|
if (response.headers) {
|
||||||
|
const header = response.headers.find((header) => header[0] === headerName);
|
||||||
|
if (header && header.length > 1) {
|
||||||
|
return header[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileNameFromContentDispositionHeader = () => {
|
||||||
|
const contentDisposition = getHeaderValue('content-disposition');
|
||||||
|
try {
|
||||||
|
const disposition = contentDispositionParser.parse(contentDisposition);
|
||||||
|
return disposition && disposition.parameters['filename'];
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileNameFromUrlPath = () => {
|
||||||
|
const lastPathLevel = new URL(url).pathname.split('/').pop();
|
||||||
|
if (lastPathLevel && /\..+/.exec(lastPathLevel)) {
|
||||||
|
return lastPathLevel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileNameBasedOnContentTypeHeader = () => {
|
||||||
|
const contentType = getHeaderValue('content-type');
|
||||||
|
const extension = (contentType && mime.extension(contentType)) || 'txt';
|
||||||
|
return `response.${extension}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileName =
|
||||||
|
getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader();
|
||||||
|
|
||||||
|
const filePath = await chooseFileToSave(mainWindow, fileName);
|
||||||
|
if (filePath) {
|
||||||
|
await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, 'base64'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = registerNetworkIpc;
|
module.exports = registerNetworkIpc;
|
||||||
|
@ -23,6 +23,10 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on('main:open-preferences', () => {
|
||||||
|
mainWindow.webContents.send('main:open-preferences');
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('renderer:save-preferences', async (event, preferences) => {
|
ipcMain.handle('renderer:save-preferences', async (event, preferences) => {
|
||||||
try {
|
try {
|
||||||
await savePreferences(preferences);
|
await savePreferences(preferences);
|
||||||
|
@ -60,6 +60,14 @@ const writeFile = async (pathname, content) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const writeBinaryFile = async (pathname, content) => {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(pathname, content);
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const hasJsonExtension = (filename) => {
|
const hasJsonExtension = (filename) => {
|
||||||
if (!filename || typeof filename !== 'string') return false;
|
if (!filename || typeof filename !== 'string') return false;
|
||||||
return ['json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`));
|
return ['json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`));
|
||||||
@ -95,6 +103,14 @@ const browseDirectory = async (win) => {
|
|||||||
return isDirectory(resolvedPath) ? resolvedPath : false;
|
return isDirectory(resolvedPath) ? resolvedPath : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const chooseFileToSave = async (win, preferredFileName = '') => {
|
||||||
|
const { filePath } = await dialog.showSaveDialog(win, {
|
||||||
|
defaultPath: preferredFileName
|
||||||
|
});
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
};
|
||||||
|
|
||||||
const searchForFiles = (dir, extension) => {
|
const searchForFiles = (dir, extension) => {
|
||||||
let results = [];
|
let results = [];
|
||||||
const files = fs.readdirSync(dir);
|
const files = fs.readdirSync(dir);
|
||||||
@ -126,10 +142,12 @@ module.exports = {
|
|||||||
isDirectory,
|
isDirectory,
|
||||||
normalizeAndResolvePath,
|
normalizeAndResolvePath,
|
||||||
writeFile,
|
writeFile,
|
||||||
|
writeBinaryFile,
|
||||||
hasJsonExtension,
|
hasJsonExtension,
|
||||||
hasBruExtension,
|
hasBruExtension,
|
||||||
createDirectory,
|
createDirectory,
|
||||||
browseDirectory,
|
browseDirectory,
|
||||||
|
chooseFileToSave,
|
||||||
searchForFiles,
|
searchForFiles,
|
||||||
searchForBruFiles,
|
searchForBruFiles,
|
||||||
sanitizeDirectoryName
|
sanitizeDirectoryName
|
||||||
|
@ -59,10 +59,24 @@ class Bru {
|
|||||||
throw new Error('Key is required');
|
throw new Error('Key is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/^(?!\d)\w*$/.test(key) === false) {
|
||||||
|
throw new Error(
|
||||||
|
`Variable name: "${key}" contains invalid characters!` +
|
||||||
|
' Names must only contain alpha-numeric characters and cannot start with a digit.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.collectionVariables[key] = value;
|
this.collectionVariables[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVar(key) {
|
getVar(key) {
|
||||||
|
if (/^(?!\d)\w*$/.test(key) === false) {
|
||||||
|
throw new Error(
|
||||||
|
`Variable name: "${key}" contains invalid characters!` +
|
||||||
|
' Names must only contain alpha-numeric characters and cannot start with a digit.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return this.collectionVariables[key];
|
return this.collectionVariables[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,11 @@ class ScriptRuntime {
|
|||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
|
const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
|
||||||
|
const additionalContextRootsAbsolute = lodash
|
||||||
|
.chain(additionalContextRoots)
|
||||||
|
.map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
|
||||||
|
.value();
|
||||||
|
|
||||||
const whitelistedModules = {};
|
const whitelistedModules = {};
|
||||||
|
|
||||||
@ -83,7 +88,7 @@ class ScriptRuntime {
|
|||||||
require: {
|
require: {
|
||||||
context: 'sandbox',
|
context: 'sandbox',
|
||||||
external: true,
|
external: true,
|
||||||
root: [collectionPath],
|
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||||
mock: {
|
mock: {
|
||||||
// node libs
|
// node libs
|
||||||
path,
|
path,
|
||||||
|
@ -48,6 +48,11 @@ class TestRuntime {
|
|||||||
const res = new BrunoResponse(response);
|
const res = new BrunoResponse(response);
|
||||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
|
const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
|
||||||
|
const additionalContextRootsAbsolute = lodash
|
||||||
|
.chain(additionalContextRoots)
|
||||||
|
.map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
|
||||||
|
.value();
|
||||||
|
|
||||||
const whitelistedModules = {};
|
const whitelistedModules = {};
|
||||||
|
|
||||||
@ -101,7 +106,7 @@ class TestRuntime {
|
|||||||
require: {
|
require: {
|
||||||
context: 'sandbox',
|
context: 'sandbox',
|
||||||
external: true,
|
external: true,
|
||||||
root: [collectionPath],
|
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||||
mock: {
|
mock: {
|
||||||
// node libs
|
// node libs
|
||||||
path,
|
path,
|
||||||
|
@ -66,7 +66,7 @@ body:sparql {
|
|||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
apikey: secret
|
apikey: secret
|
||||||
numbers: +91998877665
|
numbers: %2B91998877665
|
||||||
~message: hello
|
~message: hello
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ const devServer = async (dir, port) => {
|
|||||||
// Build the renderer code and watch the files
|
// Build the renderer code and watch the files
|
||||||
await next.prepare();
|
await next.prepare();
|
||||||
|
|
||||||
// NextJS Server
|
// Next.js Server
|
||||||
const server = createServer(requestHandler);
|
const server = createServer(requestHandler);
|
||||||
|
|
||||||
server.listen(port || 8000, () => {
|
server.listen(port || 8000, () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### Publishing Bruno to a new package manager
|
### Publishing Bruno to a new package manager
|
||||||
|
|
||||||
While our code is open source and available for everyone to use, we kindly request that you reach out to us before considering publication on new package managers. As the creator of Bruno, I hold the trademark `Bruno` for this project and would like to manage its distribution. If you'd like to see Bruno on a new package manager, please raise a Github issue.
|
While our code is open source and available for everyone to use, we kindly request that you reach out to us before considering publication on new package managers. As the creator of Bruno, I hold the trademark `Bruno` for this project and would like to manage its distribution. If you'd like to see Bruno on a new package manager, please raise a GitHub issue.
|
||||||
|
|
||||||
While, majority of our features are free and open source (which covers REST and GraphQL Apis)
|
While the majority of our features are free and open source (which covers REST and GraphQL Apis),
|
||||||
We strive to strike a harmonious balance between open-source principles and sustainability - https://github.com/usebruno/bruno/discussions/269
|
we strive to strike a harmonious balance between open-source principles and sustainability - https://github.com/usebruno/bruno/discussions/269
|
||||||
|
@ -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_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md)
|
**English** | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | [Français](docs/readme/readme_fr.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.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user