mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 01:48:33 +00:00
Compare commits
81 Commits
fix/json-s
...
v1.30.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03e7c27d8d | ||
|
|
fc79436787 | ||
|
|
6c6693757e | ||
|
|
bad1302cb5 | ||
|
|
f5a4525161 | ||
|
|
d7ff4e7ee0 | ||
|
|
637e53421e | ||
|
|
b60c799645 | ||
|
|
563683b5c1 | ||
|
|
e019a96cd5 | ||
|
|
dd2b93e8cd | ||
|
|
89c8956523 | ||
|
|
00fcd30348 | ||
|
|
07baa63e9d | ||
|
|
938e0560a2 | ||
|
|
8b6e55dfc0 | ||
|
|
572c7ea2ae | ||
|
|
260996a0ce | ||
|
|
3cb3f8094f | ||
|
|
4f7cefe41d | ||
|
|
238c790f9b | ||
|
|
5e4a96792e | ||
|
|
4419634db7 | ||
|
|
19501812fc | ||
|
|
6d239929da | ||
|
|
0ddfca4c22 | ||
|
|
50d93bc249 | ||
|
|
ea9111748f | ||
|
|
721d0e1e49 | ||
|
|
f31c997fed | ||
|
|
3dfb27d447 | ||
|
|
7dd639192c | ||
|
|
b3c72b1640 | ||
|
|
e680d0d71d | ||
|
|
1057f54f2f | ||
|
|
d971aa9596 | ||
|
|
3d5ae98e04 | ||
|
|
83e505979c | ||
|
|
0d3fde5efd | ||
|
|
0937bab7f5 | ||
|
|
8856e8ec71 | ||
|
|
9614ab069f | ||
|
|
81d8c30d84 | ||
|
|
a08573f120 | ||
|
|
776866b3b4 | ||
|
|
9a57f3870f | ||
|
|
98a7aa1357 | ||
|
|
087bab6fb4 | ||
|
|
0b4e9e7640 | ||
|
|
1c0ff13483 | ||
|
|
5d7f44fc61 | ||
|
|
c85d7b0c77 | ||
|
|
ab8afed8f9 | ||
|
|
450b1d3ae3 | ||
|
|
2191550061 | ||
|
|
4bd31fb083 | ||
|
|
bcc8811f65 | ||
|
|
a10e6ee858 | ||
|
|
46017a6c50 | ||
|
|
cb395e7649 | ||
|
|
5931f0bb4e | ||
|
|
2a93a6fa65 | ||
|
|
8a233fb489 | ||
|
|
b102898709 | ||
|
|
c5c343c543 | ||
|
|
c1ec95dc29 | ||
|
|
93080de2a8 | ||
|
|
36ef38be6a | ||
|
|
4726f5008e | ||
|
|
091b02c2c3 | ||
|
|
600940226c | ||
|
|
ee7f886c03 | ||
|
|
682c7bd1b1 | ||
|
|
e1aa5b4eb5 | ||
|
|
6bd9d4c480 | ||
|
|
a38d09a117 | ||
|
|
82985d1b43 | ||
|
|
d34d3a45ff | ||
|
|
25ccb38202 | ||
|
|
5f6a5f59b1 | ||
|
|
9e5148f032 |
@@ -15,6 +15,7 @@
|
||||
| [正體中文](docs/contributing/contributing_zhtw.md)
|
||||
| [日本語](docs/contributing/contributing_ja.md)
|
||||
| [हिंदी](docs/contributing/contributing_hi.md)
|
||||
| [Nederlands](docs/contributing/contributing_nl.md)
|
||||
|
||||
## Let's make Bruno better, together!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| **বাংলা**
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## আসুন ব্রুনোকে আরও ভালো করি, একসাথে!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| **简体中文**
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## 让我们一起改进 Bruno!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| **Deutsch**
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Lass uns Bruno noch besser machen, gemeinsam!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| **Español**
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## ¡Juntos, hagamos a Bruno mejor!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| **Français**
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Ensemble, améliorons Bruno !
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| **हिंदी**
|
||||
|
||||
## आइए मिलकर Bruno को बेहतर बनाएं !!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| **Italiano**
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Insieme, miglioriamo Bruno!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| **日本語**
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## 一緒に Bruno をよりよいものにしていきましょう!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| **한국어**
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## 함께 Bruno를 더 좋게 만들어요!!
|
||||
|
||||
|
||||
82
docs/contributing/contributing_nl.md
Normal file
82
docs/contributing/contributing_nl.md
Normal file
@@ -0,0 +1,82 @@
|
||||
[English](../../contributing.md)
|
||||
|
||||
## Laten we Bruno samen beter maken !!
|
||||
|
||||
We zijn blij dat je Bruno wilt verbeteren. Hieronder staan de richtlijnen om Bruno op je computer op te zetten.
|
||||
|
||||
### Technologiestack
|
||||
|
||||
Bruno is gebouwd met Next.js en React. We gebruiken ook Electron om een desktopversie te leveren (die lokale collecties ondersteunt).
|
||||
|
||||
Bibliotheken die we gebruiken:
|
||||
|
||||
- CSS - Tailwind
|
||||
- Code Editors - Codemirror
|
||||
- State Management - Redux
|
||||
- Iconen - Tabler Icons
|
||||
- Formulieren - formik
|
||||
- Schema Validatie - Yup
|
||||
- Request Client - axios
|
||||
- Bestandsysteem Watcher - chokidar
|
||||
|
||||
### Afhankelijkheden
|
||||
|
||||
Je hebt [Node v18.x of de nieuwste LTS-versie](https://nodejs.org/en/) en npm 8.x nodig. We gebruiken npm workspaces in het project.
|
||||
|
||||
## Ontwikkeling
|
||||
|
||||
Bruno wordt ontwikkeld als een desktop-app. Je moet de app laden door de Next.js app in één terminal te draaien en daarna de Electron app in een andere terminal te draaien.
|
||||
|
||||
### Lokale Ontwikkeling
|
||||
|
||||
```bash
|
||||
# gebruik voorgeschreven node versie
|
||||
nvm use
|
||||
|
||||
# installeer afhankelijkheden
|
||||
npm i --legacy-peer-deps
|
||||
|
||||
# build pakketten
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
|
||||
# draai next app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
# draai electron app (terminal 2)
|
||||
npm run dev:electron
|
||||
```
|
||||
|
||||
### Problemen oplossen
|
||||
|
||||
Je kunt een `Unsupported platform`-fout tegenkomen wanneer je `npm install` uitvoert. Om dit te verhelpen, moet je `node_modules` en `package-lock.json` verwijderen en `npm install` uitvoeren. Dit zou alle benodigde afhankelijkheden moeten installeren om de app te draaien.
|
||||
|
||||
```shell
|
||||
# Verwijder node_modules in subdirectories
|
||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||
rm -rf "$dir"
|
||||
done
|
||||
|
||||
# Verwijder package-lock in subdirectories
|
||||
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
|
||||
```
|
||||
|
||||
### Pull Requests indienen
|
||||
|
||||
- Houd de PR's klein en gefocust op één ding
|
||||
- Volg het formaat voor het aanmaken van branches
|
||||
- feature/[feature naam]: Deze branch moet wijzigingen voor een specifieke functie bevatten
|
||||
- Voorbeeld: feature/dark-mode
|
||||
- bugfix/[bug naam]: Deze branch moet alleen bugfixes voor een specifieke bug bevatten
|
||||
- Voorbeeld: bugfix/bug-1
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| **Polski**
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Wspólnie uczynijmy Bruno lepszym !!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| **Português (BR)**
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Vamos tornar o Bruno melhor, juntos!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| **Română**
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Haideţi să îmbunătățim Bruno, împreună!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| **Русский**
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Давайте вместе сделаем Бруно лучше!!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| **Türkçe**
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Bruno'yu birlikte daha iyi hale getirelim!!!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| **Українська**
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| [正體中文](./contributing_zhtw.md)
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## Давайте зробимо Bruno краще, разом !!
|
||||
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[English](../../contributing.md)
|
||||
| [Українська](./contributing_ua.md)
|
||||
| [Русский](./contributing_ru.md)
|
||||
| [Türkçe](./contributing_tr.md)
|
||||
| [Deutsch](./contributing_de.md)
|
||||
| [Français](./contributing_fr.md)
|
||||
| [Português (BR)](./contributing_pt_br.md)
|
||||
| [한국어](./contributing_kr.md)
|
||||
| [বাংলা](./contributing_bn.md)
|
||||
| [Español](./contributing_es.md)
|
||||
| [Italiano](./contributing_it.md)
|
||||
| [Română](./contributing_ro.md)
|
||||
| [Polski](./contributing_pl.md)
|
||||
| [简体中文](./contributing_cn.md)
|
||||
| **正體中文**
|
||||
| [日本語](./contributing_ja.md)
|
||||
| [हिंदी](./contributing_hi.md)
|
||||
|
||||
## 讓我們一起來讓 Bruno 變得更好!
|
||||
|
||||
|
||||
7
docs/publishing/publishin_nl.md
Normal file
7
docs/publishing/publishin_nl.md
Normal file
@@ -0,0 +1,7 @@
|
||||
[English](../../publishing.md)
|
||||
|
||||
### Bruno publiceren naar een nieuwe pakketbeheerder
|
||||
|
||||
Hoewel onze code open source is en beschikbaar voor iedereen, verzoeken we je vriendelijk om contact met ons op te nemen voordat je publicatie overweegt op nieuwe pakketbeheerders. Als de maker van Bruno houd ik het handelsmerk `Bruno` voor dit project en wil ik het distributieproces beheren. Als je Bruno op een nieuwe pakketbeheerder wilt zien, dien dan een GitHub-issue in.
|
||||
|
||||
Hoewel de meerderheid van onze functies gratis en open source zijn (die REST en GraphQL API's dekken), streven we ernaar een harmonieuze balans te vinden tussen open-source principes en duurzaamheid - https://github.com/usebruno/bruno/discussions/269
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| **বাংলা**
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### ব্রুনোকে নতুন প্যাকেজ ম্যানেজারে প্রকাশ করা
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| **简体中文**
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### 将 Bruno 发布到新的包管理器
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| **Deutsch**
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Veröffentlichung von Bruno über neue Paket-Manager
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| **Français**
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Publier Bruno dans un nouveau gestionnaire de paquets
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| **日本語**
|
||||
|
||||
### Bruno を新しいパッケージマネージャに公開する場合の注意
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| **Polski**
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Publikowanie Bruno w nowym menedżerze pakietów
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| **Português (BR)**
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Publicando Bruno em um novo gerenciador de pacotes
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| **Română**
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Publicarea lui Bruno la un gestionar de pachete nou
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| **Türkçe**
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| [正體中文](./publishing_zhtw.md)
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### Bruno'yu yeni bir paket yöneticisine yayınlama
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
[English](../../publishing.md)
|
||||
| [Türkçe](./publishing_tr.md)
|
||||
| [Deutsch](./publishing_de.md)
|
||||
| [Français](./publishing_fr.md)
|
||||
| [Português (BR)](./publishing_pt_br.md)
|
||||
| [বাংলা](./publishing_bn.md)
|
||||
| [Română](./publishing_ro.md)
|
||||
| [Polski](./publishing_pl.md)
|
||||
| [简体中文](./publishing_cn.md)
|
||||
| **正體中文**
|
||||
| [日本語](./publishing_ja.md)
|
||||
|
||||
### 將 Bruno 發佈到新的套件管理器
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
|
||||
|
||||
[Download Bruno](https://www.usebruno.com/downloads)
|
||||
|
||||
📢 Sehen Sie sich unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
|
||||
📢 Sieh Dir unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
|
||||
|
||||
 <br /><br />
|
||||
|
||||
@@ -48,13 +48,13 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
|
||||
Die meisten unserer Funktionen sind kostenlos und quelloffen.
|
||||
Wir bemühen uns um ein Gleichgewicht zwischen [Open-Source-Prinzipien und Nachhaltigkeit](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
Sie können die [Golden Edition](https://www.usebruno.com/pricing) vorbestellen ~~$19~~ **$9** ! <br/>
|
||||
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
|
||||
|
||||
### Installation
|
||||
|
||||
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.
|
||||
|
||||
Sie können Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
Du kannst Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
|
||||
```sh
|
||||
# Auf Mac via Homebrew
|
||||
@@ -123,11 +123,11 @@ Oder einer Versionskontrolle deiner Wahl
|
||||
|
||||
### Unterstützung ❤️
|
||||
|
||||
Wuff! Wenn du dieses Projekt magst, klick den ⭐ Button !!
|
||||
Wuff! Wenn du dieses Projekt magst, klick auf den ⭐ Button !!
|
||||
|
||||
### Teile Erfahrungsberichte 📣
|
||||
|
||||
Wenn Bruno dir und in deinen Teams bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
|
||||
Wenn Bruno dir und in deinem Team bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte in unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
|
||||
|
||||
### Bereitstellung in neuen Paket-Managern
|
||||
|
||||
|
||||
157
docs/readme/readme_nl.md
Normal file
157
docs/readme/readme_nl.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<br />
|
||||
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||
|
||||
### Bruno - Open source IDE voor het verkennen en testen van API's.
|
||||
|
||||
[](https://badge.fury.io/gh/usebruno%bruno)
|
||||
[](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
|
||||
[](https://github.com/usebruno/bruno/pulse)
|
||||
[](https://twitter.com/use_bruno)
|
||||
[](https://www.usebruno.com)
|
||||
[](https://www.usebruno.com/downloads)
|
||||
|
||||
[English](../../readme.md) | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | ** Nederlands ** | [Français](docs/readme/readme_fr.md) | [Português (BR)](docs/readme/readme_pt_br.md) | [한국어](docs/readme/readme_kr.md) | [বাংলা](docs/readme/readme_bn.md) | [Español](docs/readme/readme_es.md) | [Italiano](docs/readme/readme_it.md) | [Română](docs/readme/readme_ro.md) | [Polski](docs/readme/readme_pl.md) | [简体中文](docs/readme/readme_cn.md) | [正體中文](docs/readme/readme_zhtw.md) | [العربية](docs/readme/readme_ar.md) | [日本語](docs/readme/readme_ja.md)
|
||||
|
||||
Bruno is een nieuwe en innovatieve API-client, gericht op het revolutioneren van de status quo die wordt vertegenwoordigd door Postman en vergelijkbare tools.
|
||||
|
||||
Bruno slaat je collecties direct op in een map op je bestandssysteem. We gebruiken een platte tekst opmaaktaal, Bru, om informatie over API-verzoeken op te slaan.
|
||||
|
||||
Je kunt Git of elke versiebeheertool naar keuze gebruiken om samen te werken aan je API-collecties.
|
||||
|
||||
Bruno is uitsluitend offline. Er zijn geen plannen om ooit cloud-synchronisatie aan Bruno toe te voegen. We waarderen je gegevensprivacy en geloven dat deze op je apparaat moet blijven. Lees onze langetermijnvisie [hier](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
[Download Bruno](https://www.usebruno.com/downloads)
|
||||
|
||||
📢 Bekijk onze recente presentatie op de India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY)
|
||||
|
||||
 <br /><br />
|
||||
|
||||
### Golden Edition ✨
|
||||
|
||||
De meeste van onze functies zijn gratis en open source.
|
||||
We streven naar een harmonieuze balans tussen [open-source principes en duurzaamheid](https://github.com/usebruno/bruno/discussions/269).
|
||||
|
||||
Je kunt de [Golden Edition](https://www.usebruno.com/pricing) kopen voor een eenmalige betaling van **$19**! <br/>
|
||||
|
||||
### Installatie
|
||||
|
||||
Bruno is beschikbaar als binaire download [op onze website](https://www.usebruno.com/downloads) voor Mac, Windows en Linux.
|
||||
|
||||
Je kunt Bruno ook installeren via pakketbeheerders zoals Homebrew, Chocolatey, Scoop, Snap, Flatpak en Apt.
|
||||
|
||||
```sh
|
||||
# Op Mac via Homebrew
|
||||
brew install bruno
|
||||
|
||||
# Op Windows via Chocolatey
|
||||
choco install bruno
|
||||
|
||||
# Op Windows via Scoop
|
||||
scoop bucket add extras
|
||||
scoop install bruno
|
||||
|
||||
# Op Windows via winget
|
||||
winget install Bruno.Bruno
|
||||
|
||||
# Op Linux via Snap
|
||||
snap install bruno
|
||||
|
||||
# Op Linux via Flatpak
|
||||
flatpak install com.usebruno.Bruno
|
||||
|
||||
# Op Linux via Apt
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install bruno
|
||||
```
|
||||
|
||||
### Draai op meerdere platformen 🖥️
|
||||
|
||||
 <br /><br />
|
||||
|
||||
### Samenwerken via Git 👩💻🧑💻
|
||||
|
||||
Of elk versiebeheersysteem naar keuze
|
||||
|
||||
 <br /><br />
|
||||
|
||||
### Sponsors
|
||||
|
||||
#### Gouden Sponsors
|
||||
|
||||
<img src="../../assets/images/sponsors/samagata.png" width="150"/>
|
||||
|
||||
#### Zilveren Sponsors
|
||||
|
||||
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
|
||||
|
||||
#### Bronzen Sponsors
|
||||
|
||||
<a href="https://zuplo.link/bruno">
|
||||
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
|
||||
</a>
|
||||
|
||||
### Belangrijke Links 📌
|
||||
|
||||
- [Onze Langetermijnvisie](https://github.com/usebruno/bruno/discussions/269)
|
||||
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
|
||||
- [Documentatie](https://docs.usebruno.com)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno)
|
||||
- [Website](https://www.usebruno.com)
|
||||
- [Prijzen](https://www.usebruno.com/pricing)
|
||||
- [Download](https://www.usebruno.com/downloads)
|
||||
- [GitHub Sponsors](https://github.com/sponsors/helloanoop)
|
||||
|
||||
### Showcase 🎥
|
||||
|
||||
- [Getuigenissen](https://github.com/usebruno/bruno/discussions/343)
|
||||
- [Kenniscentrum](https://github.com/usebruno/bruno/discussions/386)
|
||||
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||
|
||||
### Ondersteuning ❤️
|
||||
|
||||
Als je Bruno leuk vindt en ons open-source werk wilt ondersteunen, overweeg dan om ons te sponsoren via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
|
||||
|
||||
### Deel Getuigenissen 📣
|
||||
|
||||
Als Bruno je heeft geholpen op je werk en in je teams, deel dan je [getuigenissen op onze GitHub-discussie](https://github.com/usebruno/bruno/discussions/343).
|
||||
|
||||
|
||||
### Blijf in 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)
|
||||
|
||||
### Handelsmerk
|
||||
|
||||
**Naam**
|
||||
|
||||
`Bruno` is een handelsmerk in bezit van [Anoop M D](https://www.helloanoop.com/).
|
||||
|
||||
**Logo**
|
||||
|
||||
Het logo is afkomstig van [OpenMoji](https://openmoji.org/library/emoji-1F436/). Licentie: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
### Bijdragen 👩💻🧑💻
|
||||
|
||||
Ik ben blij dat je Bruno wilt verbeteren. Bekijk de [bijdragegids](contributing.md).
|
||||
|
||||
Zelfs als je geen bijdragen via code kunt leveren, aarzel dan niet om bugs en functieverzoeken in te dienen die moeten worden geïmplementeerd om jouw gebruiksscenario op te lossen.
|
||||
|
||||
### Auteurs
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Licentie 📄
|
||||
|
||||
[MIT](../../license.md)
|
||||
57
package-lock.json
generated
57
package-lock.json
generated
@@ -50,6 +50,7 @@
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -670,6 +671,7 @@
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -677,6 +679,7 @@
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
@@ -740,6 +743,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.25.2",
|
||||
@@ -754,6 +758,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
@@ -761,6 +766,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
@@ -839,6 +845,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
@@ -905,6 +912,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.24.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
@@ -942,6 +950,7 @@
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.24.8",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -962,6 +971,7 @@
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.25.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.0",
|
||||
@@ -3644,37 +3654,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@n8n/vm2": {
|
||||
"version": "3.9.25",
|
||||
"resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz",
|
||||
"integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.7.0",
|
||||
"acorn-walk": "^8.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"vm2": "bin/vm2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.10",
|
||||
"pnpm": ">=9.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@n8n/vm2/node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "12.3.3",
|
||||
"license": "MIT"
|
||||
@@ -4751,6 +4730,7 @@
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
@@ -4759,6 +4739,7 @@
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "12.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "*",
|
||||
@@ -4767,6 +4748,7 @@
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
@@ -6240,6 +6222,7 @@
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.3",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -7238,6 +7221,7 @@
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
@@ -8477,6 +8461,7 @@
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.11",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-util": {
|
||||
@@ -9438,6 +9423,7 @@
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -13186,6 +13172,7 @@
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-vault": {
|
||||
@@ -16201,6 +16188,7 @@
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -17663,6 +17651,7 @@
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -18578,7 +18567,6 @@
|
||||
"inquirer": "^9.1.4",
|
||||
"json-bigint": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mustache": "^4.2.0",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"vm2": "^3.9.13",
|
||||
@@ -18633,7 +18621,7 @@
|
||||
},
|
||||
"packages/bruno-electron": {
|
||||
"name": "bruno",
|
||||
"version": "v1.26.1",
|
||||
"version": "v1.28.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.525.0",
|
||||
"@usebruno/common": "0.1.0",
|
||||
@@ -18664,7 +18652,6 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"mustache": "^4.2.0",
|
||||
"nanoid": "3.3.4",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
@@ -18875,4 +18862,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ const StyledWrapper = styled.div`
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
font-family: ${(props) => (props.font ? props.font : 'default')};
|
||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||
line-break: anywhere;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { isEqual, escapeRegExp } from 'lodash';
|
||||
import { getEnvironmentVariables } from 'utils/collections';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -61,12 +61,15 @@ if (!SERVER_RENDERED) {
|
||||
'bru.getProcessEnv(key)',
|
||||
'bru.hasEnvVar(key)',
|
||||
'bru.getEnvVar(key)',
|
||||
'bru.getFolderVar(key)',
|
||||
'bru.getCollectionVar(key)',
|
||||
'bru.setEnvVar(key,value)',
|
||||
'bru.hasVar(key)',
|
||||
'bru.getVar(key)',
|
||||
'bru.setVar(key,value)',
|
||||
'bru.deleteVar(key)',
|
||||
'bru.setNextRequest(requestName)',
|
||||
'req.disableParsingResponseJson()',
|
||||
'bru.getRequestVar(key)',
|
||||
'bru.sleep(ms)'
|
||||
];
|
||||
@@ -332,6 +335,7 @@ export default class CodeEditor extends React.Component {
|
||||
className="h-full w-full flex flex-col relative"
|
||||
aria-label="Code Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
@@ -404,7 +408,8 @@ export default class CodeEditor extends React.Component {
|
||||
const searchInput = document.querySelector('.CodeMirror-search-field');
|
||||
|
||||
if (searchInput && searchInput.value.length > 0) {
|
||||
const text = new RegExp(searchInput.value, 'gi');
|
||||
// Escape special characters in search input to prevent RegExp crashes. Fixes #3051
|
||||
const text = new RegExp(escapeRegExp(searchInput.value), 'gi');
|
||||
const matches = this.editor.getValue().match(text);
|
||||
count = matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
|
||||
.auth-placement-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-type-label {
|
||||
width: fit-content;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.caret {
|
||||
color: rgb(140, 140, 140);
|
||||
fill: rgb(140 140 140);
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,109 @@
|
||||
import React, { useRef, forwardRef, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
|
||||
|
||||
const ApiKeyAuth = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const apikeyAuth = get(collection, 'root.request.auth.apikey', {});
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||
{humanizeRequestAPIKeyPlacement(apikeyAuth?.placement)}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleAuthChange = (property, value) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
...apikeyAuth,
|
||||
[property]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
!apikeyAuth?.placement &&
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
placement: 'header'
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [apikeyAuth]);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Key</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={apikeyAuth.key || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleAuthChange('key', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Value</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={apikeyAuth.value || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleAuthChange('value', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Add To</label>
|
||||
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleAuthChange('placement', 'header');
|
||||
}}
|
||||
>
|
||||
Header
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleAuthChange('placement', 'queryparams');
|
||||
}}
|
||||
>
|
||||
Query Params
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiKeyAuth;
|
||||
@@ -79,6 +79,15 @@ const AuthMode = ({ collection }) => {
|
||||
>
|
||||
Oauth2
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('apikey');
|
||||
}}
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import AwsV4Auth from './AwsV4Auth';
|
||||
import BearerAuth from './BearerAuth';
|
||||
import BasicAuth from './BasicAuth';
|
||||
import DigestAuth from './DigestAuth';
|
||||
import ApiKeyAuth from './ApiKeyAuth/';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import OAuth2 from './OAuth2';
|
||||
@@ -33,6 +34,9 @@ const Auth = ({ collection }) => {
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} />;
|
||||
}
|
||||
case 'apikey': {
|
||||
return <ApiKeyAuth collection={collection} />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ import StyledWrapper from './StyledWrapper';
|
||||
import { useRef } from 'react';
|
||||
import path from 'path';
|
||||
import slash from 'utils/common/slash';
|
||||
import { isWindowsOS } from 'utils/common/platform';
|
||||
|
||||
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
const certFilePathInputRef = useRef();
|
||||
const keyFilePathInputRef = useRef();
|
||||
const pfxFilePathInputRef = useRef();
|
||||
@@ -67,7 +68,15 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
});
|
||||
|
||||
const getFile = (e) => {
|
||||
e.files?.[0]?.path && formik.setFieldValue(e.name, e.files?.[0]?.path);
|
||||
if (e.files?.[0]?.path) {
|
||||
let relativePath;
|
||||
if (isWindowsOS()) {
|
||||
relativePath = slash(path.win32.relative(root, e.files[0].path));
|
||||
} else {
|
||||
relativePath = path.posix.relative(root, e.files[0].path);
|
||||
}
|
||||
formik.setFieldValue(e.name, relativePath);
|
||||
}
|
||||
};
|
||||
|
||||
const resetFileInputFields = () => {
|
||||
@@ -102,10 +111,14 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
: clientCertConfig.map((clientCert) => (
|
||||
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-full items-center">
|
||||
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
|
||||
{clientCert.domain}
|
||||
</div>
|
||||
<div className="flex w-full items-center">
|
||||
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
|
||||
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
|
||||
</div>
|
||||
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
|
||||
<IconTrash size={18} strokeWidth={1.5} />
|
||||
</button>
|
||||
|
||||
@@ -46,6 +46,7 @@ const Docs = ({ collection }) => {
|
||||
onSave={onSave}
|
||||
mode="application/text"
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
) : (
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import * as Yup from 'yup';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -104,7 +104,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label flex items-center" htmlFor="enabled">
|
||||
Config
|
||||
<Tooltip
|
||||
<InfoTip
|
||||
text={`
|
||||
<div>
|
||||
<ul>
|
||||
@@ -114,7 +114,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
</ul>
|
||||
</div>
|
||||
`}
|
||||
tooltipId="request-var"
|
||||
infotipId="request-var"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
@@ -336,4 +336,4 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ProxySettings;
|
||||
export default ProxySettings;
|
||||
@@ -52,6 +52,7 @@ const Script = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
@@ -64,6 +65,7 @@ const Script = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ const Tests = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.title {
|
||||
color: var(--color-tab-inactive);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,56 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-var {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
background-color: inherit;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import {
|
||||
addCollectionVar,
|
||||
deleteCollectionVar,
|
||||
updateCollectionVar
|
||||
} from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const VarsTable = ({ collection, vars, varType }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const addVar = () => {
|
||||
dispatch(
|
||||
addCollectionVar({
|
||||
collectionUid: collection.uid,
|
||||
type: varType
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
const handleVarChange = (e, v, type) => {
|
||||
const _var = cloneDeep(v);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
const value = e.target.value;
|
||||
|
||||
if (variableNameRegex.test(value) === false) {
|
||||
toast.error(
|
||||
'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_var.name = value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
_var.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
_var.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(
|
||||
updateCollectionVar({
|
||||
type: varType,
|
||||
var: _var,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveVar = (_var) => {
|
||||
dispatch(
|
||||
deleteCollectionVar({
|
||||
type: varType,
|
||||
varUid: _var.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
{varType === 'request' ? (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vars && vars.length
|
||||
? vars.map((_var) => {
|
||||
return (
|
||||
<tr key={_var.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={_var.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, _var, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={_var.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleVarChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
_var,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={_var.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, _var, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={addVar}>
|
||||
+ Add
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default VarsTable;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import VarsTable from './VarsTable';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const Vars = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const requestVars = get(collection, 'root.request.vars.req', []);
|
||||
const responseVars = get(collection, 'root.request.vars.res', []);
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<VarsTable collection={collection} vars={requestVars} varType="request" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<VarsTable collection={collection} vars={responseVars} varType="response" />
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Vars;
|
||||
@@ -16,6 +16,7 @@ import Docs from './Docs';
|
||||
import Presets from './Presets';
|
||||
import Info from './Info';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars/index';
|
||||
|
||||
const CollectionSettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -77,6 +78,9 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'headers': {
|
||||
return <Headers collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth collection={collection} />;
|
||||
}
|
||||
@@ -95,6 +99,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'clientCert': {
|
||||
return (
|
||||
<ClientCertSettings
|
||||
root={collection.pathname}
|
||||
clientCertConfig={clientCertConfig}
|
||||
onUpdate={onClientCertSettingsUpdate}
|
||||
onRemove={onClientCertSettingsRemove}
|
||||
@@ -122,6 +127,9 @@ const CollectionSettings = ({ collection }) => {
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
|
||||
Vars
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||
Auth
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,7 @@ const Documentation = ({ item, collection }) => {
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
value={docs || ''}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
|
||||
@@ -44,7 +44,7 @@ const CopyEnvironment = ({ collection, environment, onClose }) => {
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title={'Copy Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
New Environment Name
|
||||
|
||||
@@ -50,7 +50,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
Environment Name
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -9,12 +10,12 @@ import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const addButtonRef = useRef(null);
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
@@ -85,6 +86,12 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (formik.dirty) {
|
||||
addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [formik.values, formik.dirty]);
|
||||
|
||||
const handleReset = () => {
|
||||
formik.resetForm({ originalEnvironmentVariables });
|
||||
};
|
||||
@@ -159,11 +166,15 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
|
||||
+ Add Variable
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
ref={addButtonRef}
|
||||
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={addVariable}
|
||||
>
|
||||
+ Add Variable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -50,7 +50,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
Environment Name
|
||||
|
||||
@@ -116,6 +116,7 @@ const Headers = ({ collection, folder }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -54,6 +54,7 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
@@ -66,6 +67,7 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const Tests = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
@@ -82,14 +82,14 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
@@ -130,6 +130,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import { Tooltip as ReactInfoTip } from 'react-tooltip';
|
||||
|
||||
const Tooltip = ({ text, tooltipId }) => {
|
||||
const InfoTip = ({ text, infotipId }) => {
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
tabIndex="-1"
|
||||
id={tooltipId}
|
||||
id={infotipId}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -17,9 +17,9 @@ const Tooltip = ({ text, tooltipId }) => {
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
|
||||
</svg>
|
||||
<ReactTooltip anchorId={tooltipId} html={text} />
|
||||
<ReactInfoTip anchorId={infotipId} html={text} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
export default InfoTip;
|
||||
@@ -2,6 +2,7 @@ import MarkdownIt from 'markdown-it';
|
||||
import * as MarkdownItReplaceLink from 'markdown-it-replace-link';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import React from 'react';
|
||||
import { isValidUrl } from 'utils/url/index';
|
||||
|
||||
const Markdown = ({ collectionPath, onDoubleClick, content }) => {
|
||||
const markdownItOptions = {
|
||||
@@ -15,7 +16,7 @@ const Markdown = ({ collectionPath, onDoubleClick, content }) => {
|
||||
if (target.tagName === 'A') {
|
||||
event.preventDefault();
|
||||
const href = target.getAttribute('href');
|
||||
if (href) {
|
||||
if (href && isValidUrl(href)) {
|
||||
window.open(href, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import useFocusTrap from 'hooks/useFocusTrap';
|
||||
|
||||
const ESC_KEY_CODE = 27;
|
||||
const ENTER_KEY_CODE = 13;
|
||||
|
||||
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
|
||||
<div className="bruno-modal-header">
|
||||
@@ -69,25 +73,35 @@ const Modal = ({
|
||||
onClick,
|
||||
closeModalFadeTimeout = 500
|
||||
}) => {
|
||||
const modalRef = useRef(null);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const escFunction = (event) => {
|
||||
const escKeyCode = 27;
|
||||
if (event.keyCode === escKeyCode) {
|
||||
closeModal({ type: 'esc' });
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
const { keyCode, shiftKey, ctrlKey, altKey, metaKey } = event;
|
||||
switch (keyCode) {
|
||||
case ESC_KEY_CODE: {
|
||||
if (disableEscapeKey) return;
|
||||
return closeModal({ type: 'esc' });
|
||||
}
|
||||
case ENTER_KEY_CODE: {
|
||||
if (!shiftKey && !ctrlKey && !altKey && !metaKey && handleConfirm) {
|
||||
return handleConfirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useFocusTrap(modalRef);
|
||||
|
||||
const closeModal = (args) => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (disableEscapeKey) return;
|
||||
document.addEventListener('keydown', escFunction, false);
|
||||
|
||||
document.addEventListener('keydown', handleKeydown, false);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', escFunction, false);
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
};
|
||||
}, [disableEscapeKey, document]);
|
||||
|
||||
@@ -100,7 +114,13 @@ const Modal = ({
|
||||
}
|
||||
return (
|
||||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<div
|
||||
className={`bruno-modal-card modal-${size}`}
|
||||
ref={modalRef}
|
||||
role="dialog"
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
<ModalHeader
|
||||
title={title}
|
||||
hideClose={hideClose}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from 'providers/ReduxStore/slices/notifications';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { humanizeDate, relativeDate } from 'utils/common';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
@@ -20,6 +22,7 @@ const Notifications = () => {
|
||||
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
|
||||
const [selectedNotification, setSelectedNotification] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const notificationsStartIndex = (pageNumber - 1) * PAGE_SIZE;
|
||||
const notificationsEndIndex = pageNumber * PAGE_SIZE;
|
||||
@@ -85,21 +88,24 @@ const Notifications = () => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<a
|
||||
title="Notifications"
|
||||
className="relative cursor-pointer"
|
||||
onClick={() => {
|
||||
dispatch(fetchNotifications());
|
||||
setShowNotificationsModal(true);
|
||||
}}
|
||||
aria-label="Check all Notifications"
|
||||
>
|
||||
<IconBell
|
||||
size={18}
|
||||
strokeWidth={1.5}
|
||||
className={`mr-2 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
/>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<span className="notification-count text-xs">{unreadNotifications.length}</span>
|
||||
)}
|
||||
<ToolHint text="Notifications" toolhintId="Notifications" offset={8}>
|
||||
<IconBell
|
||||
size={18}
|
||||
aria-hidden
|
||||
strokeWidth={1.5}
|
||||
className={`mr-2 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
/>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<span className="notification-count text-xs">{unreadNotifications.length}</span>
|
||||
)}
|
||||
</ToolHint>
|
||||
</a>
|
||||
|
||||
{showNotificationsModal && (
|
||||
|
||||
@@ -9,17 +9,25 @@ const Font = ({ close }) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
|
||||
const [codeFontSize, setCodeFontSize] = useState(get(preferences, 'font.codeFontSize', '14'));
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const handleCodeFontChange = (event) => {
|
||||
setCodeFont(event.target.value);
|
||||
};
|
||||
|
||||
const handleCodeFontSizeChange = (event) => {
|
||||
// Restrict to min/max value
|
||||
const clampedSize = Math.max(1, Math.min(event.target.value, 32));
|
||||
setCodeFontSize(clampedSize);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(
|
||||
savePreferences({
|
||||
...preferences,
|
||||
font: {
|
||||
codeFont
|
||||
codeFont,
|
||||
codeFontSize
|
||||
}
|
||||
})
|
||||
).then(() => {
|
||||
@@ -29,17 +37,33 @@ const Font = ({ close }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<label className="block font-medium">Code Editor Font</label>
|
||||
<input
|
||||
type="text"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleInputChange}
|
||||
defaultValue={codeFont}
|
||||
/>
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<div className="w-4/5">
|
||||
<label className="block font-medium">Code Editor Font</label>
|
||||
<input
|
||||
type="text"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleCodeFontChange}
|
||||
defaultValue={codeFont}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/5">
|
||||
<label className="block font-medium">Font Size</label>
|
||||
<input
|
||||
type="number"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
inputMode="numeric"
|
||||
onChange={handleCodeFontSizeChange}
|
||||
defaultValue={codeFontSize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.settings-label {
|
||||
width: 80px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
@@ -20,6 +20,12 @@ const StyledWrapper = styled.div`
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.system-proxy-settings {
|
||||
label {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -11,14 +11,17 @@ import { useState } from 'react';
|
||||
|
||||
const ProxySettings = ({ close }) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const systemProxyEnvVariables = useSelector((state) => state.app.systemProxyEnvVariables);
|
||||
const { http_proxy, https_proxy, no_proxy } = systemProxyEnvVariables || {};
|
||||
const dispatch = useDispatch();
|
||||
console.log(preferences);
|
||||
|
||||
const proxySchema = Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
mode: Yup.string().oneOf(['off', 'on', 'system']),
|
||||
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||
hostname: Yup.string()
|
||||
.when('enabled', {
|
||||
is: true,
|
||||
is: 'on',
|
||||
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
|
||||
otherwise: (hostname) => hostname.nullable()
|
||||
})
|
||||
@@ -31,7 +34,7 @@ const ProxySettings = ({ close }) => {
|
||||
.transform((_, val) => (val ? Number(val) : null)),
|
||||
auth: Yup.object()
|
||||
.when('enabled', {
|
||||
is: true,
|
||||
is: 'on',
|
||||
then: Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
username: Yup.string()
|
||||
@@ -54,7 +57,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
mode: preferences.proxy.mode,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || 0,
|
||||
@@ -94,7 +97,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
useEffect(() => {
|
||||
formik.setValues({
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
mode: preferences.proxy.mode,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || '',
|
||||
@@ -109,188 +112,256 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="enabled">
|
||||
Enabled
|
||||
</label>
|
||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Protocol
|
||||
Mode
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="http"
|
||||
checked={formik.values.protocol === 'http'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
name="mode"
|
||||
value="false"
|
||||
checked={formik.values.mode === 'off'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('mode', 'off');
|
||||
}}
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
HTTP
|
||||
Off
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<label className="flex items-center ml-4 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="https"
|
||||
checked={formik.values.protocol === 'https'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
name="mode"
|
||||
value="true"
|
||||
checked={formik.values.mode === 'on'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('mode', 'on');
|
||||
}}
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
HTTPS
|
||||
On
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<label className="flex items-center ml-4 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks4"
|
||||
checked={formik.values.protocol === 'socks4'}
|
||||
name="mode"
|
||||
value="system"
|
||||
checked={formik.values.mode === 'system'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
SOCKS4
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks5"
|
||||
checked={formik.values.protocol === 'socks5'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS5
|
||||
System Proxy
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
</label>
|
||||
<input
|
||||
id="hostname"
|
||||
type="text"
|
||||
name="hostname"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.hostname || ''}
|
||||
/>
|
||||
{formik.touched.hostname && formik.errors.hostname ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="port">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
id="port"
|
||||
type="number"
|
||||
name="port"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.port}
|
||||
/>
|
||||
{formik.touched.port && formik.errors.port ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.enabled">
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="auth.enabled"
|
||||
checked={formik.values.auth.enabled}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="auth.username"
|
||||
type="text"
|
||||
name="auth.username"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.username}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.username && formik.errors.auth?.username ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
||||
) : null}
|
||||
{formik?.values?.mode === 'system' ? (
|
||||
<div className="mb-3 pt-1 text-muted system-proxy-settings">
|
||||
<small>
|
||||
Below values are sourced from your system environment variables and cannot be directly updated in Bruno.<br/>
|
||||
Please refer to your OS documentation to change these values.
|
||||
</small>
|
||||
<div className="flex flex-col justify-start items-start pt-2">
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="http_proxy">
|
||||
http_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{http_proxy || '-'}</div>
|
||||
</div>
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="https_proxy">
|
||||
https_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{https_proxy || '-'}</div>
|
||||
</div>
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="no_proxy">
|
||||
no_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{no_proxy || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.password">
|
||||
Password
|
||||
</label>
|
||||
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
|
||||
) : null}
|
||||
{formik?.values?.mode === 'on' ? (
|
||||
<>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Protocol
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="http"
|
||||
checked={formik.values.protocol === 'http'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
HTTP
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="https"
|
||||
checked={formik.values.protocol === 'https'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
HTTPS
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks4"
|
||||
checked={formik.values.protocol === 'socks4'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS4
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks5"
|
||||
checked={formik.values.protocol === 'socks5'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS5
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
</label>
|
||||
<input
|
||||
id="auth.password"
|
||||
type={passwordVisible ? `text` : 'password'}
|
||||
name="auth.password"
|
||||
className="outline-none w-[10.5rem] bg-transparent"
|
||||
id="hostname"
|
||||
type="text"
|
||||
name="hostname"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.password}
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.hostname || ''}
|
||||
/>
|
||||
{formik.touched.hostname && formik.errors.hostname ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="port">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
id="port"
|
||||
type="number"
|
||||
name="port"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.port}
|
||||
/>
|
||||
{formik.touched.port && formik.errors.port ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.enabled">
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="auth.enabled"
|
||||
checked={formik.values.auth.enabled}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm absolute right-0"
|
||||
onClick={() => setPasswordVisible(!passwordVisible)}
|
||||
>
|
||||
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
|
||||
</button>
|
||||
</div>
|
||||
{formik.touched.auth?.password && formik.errors.auth?.password ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="bypassProxy">
|
||||
Proxy Bypass
|
||||
</label>
|
||||
<input
|
||||
id="bypassProxy"
|
||||
type="text"
|
||||
name="bypassProxy"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.bypassProxy || ''}
|
||||
/>
|
||||
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="auth.username"
|
||||
type="text"
|
||||
name="auth.username"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.username}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.username && formik.errors.auth?.username ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.password">
|
||||
Password
|
||||
</label>
|
||||
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
|
||||
<input
|
||||
id="auth.password"
|
||||
type={passwordVisible ? `text` : 'password'}
|
||||
name="auth.password"
|
||||
className="outline-none w-[10.5rem] bg-transparent"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.password}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm absolute right-0"
|
||||
onClick={() => setPasswordVisible(!passwordVisible)}
|
||||
>
|
||||
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
|
||||
</button>
|
||||
</div>
|
||||
{formik.touched.auth?.password && formik.errors.auth?.password ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="bypassProxy">
|
||||
Proxy Bypass
|
||||
</label>
|
||||
<input
|
||||
id="bypassProxy"
|
||||
type="text"
|
||||
name="bypassProxy"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.bypassProxy || ''}
|
||||
/>
|
||||
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
||||
Save
|
||||
|
||||
112
packages/bruno-app/src/components/ReorderTable/index.js
Normal file
112
packages/bruno-app/src/components/ReorderTable/index.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { IconGripVertical, IconMinusVertical } from '@tabler/icons';
|
||||
|
||||
/**
|
||||
* ReorderTable Component
|
||||
*
|
||||
* A table component that allows rows to be reordered via drag-and-drop.
|
||||
*
|
||||
* @param {Object} props - The component props
|
||||
* @param {React.ReactNode[]} props.children - The table rows as children
|
||||
* @param {function} props.updateReorderedItem - Callback function to handle reordered rows
|
||||
*/
|
||||
|
||||
const ReorderTable = ({ children, updateReorderedItem }) => {
|
||||
const tbodyRef = useRef();
|
||||
const [rowsOrder, setRowsOrder] = useState(React.Children.toArray(children));
|
||||
const [hoveredRow, setHoveredRow] = useState(null);
|
||||
const [dragStart, setDragStart] = useState(null);
|
||||
|
||||
/**
|
||||
* useEffect hook to update the rows order and handle row hover states
|
||||
*/
|
||||
useEffect(() => {
|
||||
setRowsOrder(React.Children.toArray(children));
|
||||
handleRowHover(null, false);
|
||||
}, [children, dragStart]);
|
||||
|
||||
const handleRowHover = (index, hoverstatus = true) => {
|
||||
setHoveredRow(hoverstatus ? index : null);
|
||||
};
|
||||
|
||||
const handleDragStart = (e, index) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', index);
|
||||
setDragStart(index);
|
||||
};
|
||||
|
||||
const handleDragOver = (e, index) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
handleRowHover(index);
|
||||
};
|
||||
|
||||
const handleDrop = (e, toIndex) => {
|
||||
e.preventDefault();
|
||||
const fromIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
|
||||
if (fromIndex !== toIndex) {
|
||||
const updatedRowsOrder = [...rowsOrder];
|
||||
const [movedRow] = updatedRowsOrder.splice(fromIndex, 1);
|
||||
updatedRowsOrder.splice(toIndex, 0, movedRow);
|
||||
setRowsOrder(updatedRowsOrder);
|
||||
|
||||
updateReorderedItem({
|
||||
updateReorderedItem: updatedRowsOrder.map((row) => row.props['data-uid'])
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
handleRowHover(toIndex);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<tbody ref={tbodyRef}>
|
||||
{rowsOrder.map((row, index) => (
|
||||
<tr
|
||||
key={row.props['data-uid']}
|
||||
data-uid={row.props['data-uid']}
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, index)}
|
||||
onDragOver={(e) => handleDragOver(e, index)}
|
||||
onDrop={(e) => handleDrop(e, index)}
|
||||
onMouseEnter={() => handleRowHover(index)}
|
||||
onMouseLeave={() => handleRowHover(index, false)}
|
||||
>
|
||||
{React.Children.map(row.props.children, (child, childIndex) => {
|
||||
if (childIndex === 0) {
|
||||
return React.cloneElement(child, {
|
||||
children: (
|
||||
<>
|
||||
<div
|
||||
draggable
|
||||
className="group drag-handle absolute z-10 left-[-17px] p-3.5 py-3.5 px-2.5 top-[3px] cursor-grab"
|
||||
>
|
||||
{hoveredRow === index && (
|
||||
<>
|
||||
<IconGripVertical
|
||||
size={14}
|
||||
className="z-10 icon-grip rounded-md absolute hidden group-hover:block"
|
||||
/>
|
||||
<IconMinusVertical
|
||||
size={14}
|
||||
className="z-10 icon-minus rounded-md absolute block group-hover:hidden"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{child.props.children}
|
||||
</>
|
||||
)
|
||||
});
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReorderTable;
|
||||
@@ -191,6 +191,7 @@ const AssertionRow = ({
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
) : (
|
||||
<input type="text" className="cursor-default" disabled />
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
}
|
||||
|
||||
.auth-placement-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-type-label {
|
||||
width: fit-content;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.caret {
|
||||
color: rgb(140, 140, 140);
|
||||
fill: rgb(140 140 140);
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { useRef, forwardRef, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import get from 'lodash/get';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAPIKeyPlacement } from 'utils/collections';
|
||||
|
||||
const ApiKeyAuth = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const apikeyAuth = item.draft ? get(item, 'draft.request.auth.apikey', {}) : get(item, 'request.auth.apikey', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end auth-type-label select-none">
|
||||
{humanizeRequestAPIKeyPlacement(apikeyAuth?.placement)}
|
||||
<IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleAuthChange = (property, value) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
...apikeyAuth,
|
||||
[property]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
!apikeyAuth?.placement &&
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
placement: 'header'
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [apikeyAuth]);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Key</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={apikeyAuth.key || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleAuthChange('key', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Value</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={apikeyAuth.value || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleAuthChange('value', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Add To</label>
|
||||
<div className="inline-flex items-center cursor-pointer auth-placement-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleAuthChange('placement', 'header');
|
||||
}}
|
||||
>
|
||||
Header
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleAuthChange('placement', 'queryparams');
|
||||
}}
|
||||
>
|
||||
Query Params
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiKeyAuth;
|
||||
@@ -80,6 +80,15 @@ const AuthMode = ({ item, collection }) => {
|
||||
>
|
||||
OAuth 2.0
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('apikey');
|
||||
}}
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import AwsV4Auth from './AwsV4Auth';
|
||||
import BearerAuth from './BearerAuth';
|
||||
import BasicAuth from './BasicAuth';
|
||||
import DigestAuth from './DigestAuth';
|
||||
import ApiKeyAuth from './ApiKeyAuth';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
||||
import OAuth2 from './OAuth2/index';
|
||||
@@ -32,6 +33,9 @@ const Auth = ({ item, collection }) => {
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} item={item} />;
|
||||
}
|
||||
case 'apikey': {
|
||||
return <ApiKeyAuth collection={collection} item={item} />;
|
||||
}
|
||||
case 'inherit': {
|
||||
return (
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
|
||||
@@ -31,6 +31,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
: get(item, 'request.body.graphql.variables');
|
||||
const { displayedTheme } = useTheme();
|
||||
const [schema, setSchema] = useState(null);
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
useEffect(() => {
|
||||
onSchemaLoad(schema);
|
||||
@@ -71,6 +72,8 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
onRun={onRun}
|
||||
onEdit={onQueryChange}
|
||||
onClickReference={handleGqlClickReference}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/colle
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { format, applyEdits } from 'jsonc-parser';
|
||||
import { IconWand } from '@tabler/icons';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -13,6 +16,25 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onPrettify = () => {
|
||||
if (!variables) return;
|
||||
try {
|
||||
const edits = format(variables, undefined, { tabSize: 2, insertSpaces: true });
|
||||
const prettyVariables = applyEdits(variables, edits);
|
||||
dispatch(
|
||||
updateRequestGraphqlVariables({
|
||||
variables: prettyVariables,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
toast.success('Variables prettified');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Error occurred while prettifying GraphQL variables');
|
||||
}
|
||||
};
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
updateRequestGraphqlVariables({
|
||||
@@ -27,12 +49,20 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<StyledWrapper className="w-full relative">
|
||||
<button
|
||||
className="btn-add-param text-link px-4 py-4 select-none absolute top-0 right-0 z-10"
|
||||
onClick={onPrettify}
|
||||
title={'Prettify'}
|
||||
>
|
||||
<IconWand size={20} strokeWidth={1.5} />
|
||||
</button>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={variables || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -97,6 +97,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
const script = getPropertyFromDraftOrRequest('request.script');
|
||||
const assertions = getPropertyFromDraftOrRequest('request.assertions');
|
||||
const tests = getPropertyFromDraftOrRequest('request.tests');
|
||||
const docs = getPropertyFromDraftOrRequest('request.docs');
|
||||
const requestVars = getPropertyFromDraftOrRequest('request.vars.req');
|
||||
const responseVars = getPropertyFromDraftOrRequest('request.vars.res');
|
||||
|
||||
@@ -139,10 +140,11 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
</div>
|
||||
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
|
||||
Tests
|
||||
{tests && <ContentIndicator />}
|
||||
{tests && tests.length > 0 && <ContentIndicator />}
|
||||
</div>
|
||||
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
|
||||
Docs
|
||||
{docs && docs.length > 0 && <ContentIndicator />}
|
||||
</div>
|
||||
{focusedTab.requestPaneTab === 'body' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
|
||||
@@ -4,6 +4,8 @@ const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
font-family: ${(props) => (props.font ? props.font : 'default')};
|
||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,6 +211,8 @@ export default class QueryEditor extends React.Component {
|
||||
<StyledWrapper
|
||||
className="h-full w-full flex flex-col relative"
|
||||
aria-label="Query Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
|
||||
@@ -22,14 +22,12 @@ const Wrapper = styled.div`
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
td {
|
||||
&:nth-child(1) {
|
||||
padding: 0 0 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import {
|
||||
addQueryParam,
|
||||
updateQueryParam,
|
||||
deleteQueryParam,
|
||||
updatePathParam,
|
||||
updateQueryParam
|
||||
moveQueryParam,
|
||||
updatePathParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Table from 'components/Table/index';
|
||||
import ReorderTable from 'components/ReorderTable';
|
||||
|
||||
const QueryParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -100,82 +103,81 @@ const QueryParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||
dispatch(
|
||||
moveQueryParam({
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
updateReorderedItem
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<StyledWrapper className="w-full flex flex-col absolute">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-2 title text-xs">Query</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div className="mb-1 title text-xs">Query</div>
|
||||
|
||||
<Table
|
||||
headers={[
|
||||
{ name: 'Name', accessor: 'name', width: '31%' },
|
||||
{ name: 'Path', accessor: 'path', width: '56%' },
|
||||
{ name: '', accessor: '', width: '13%' }
|
||||
]}
|
||||
>
|
||||
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||
{queryParams && queryParams.length
|
||||
? queryParams.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
? queryParams.map((param, index) => (
|
||||
<tr key={param.uid} data-uid={param.uid}>
|
||||
<td className="flex relative">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleQueryParamChange({ target: { value: newValue } }, param, 'value')}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
variablesAutocomplete={true}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'name')}
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleQueryParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</ReorderTable>
|
||||
</Table>
|
||||
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddQueryParam}>
|
||||
+ <span>Add Param</span>
|
||||
</button>
|
||||
<div className="mb-2 title text-xs flex items-stretch">
|
||||
<span>Path</span>
|
||||
<Tooltip
|
||||
<InfoTip
|
||||
text={`
|
||||
<div>
|
||||
Path variables are automatically added whenever the
|
||||
@@ -186,7 +188,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
</code>
|
||||
</div>
|
||||
`}
|
||||
tooltipId="path-param-tooltip"
|
||||
infotipId="path-param-InfoTip"
|
||||
/>
|
||||
</div>
|
||||
<table>
|
||||
|
||||
@@ -33,18 +33,18 @@ const Wrapper = styled.div`
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
.infotip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
.infotip:hover .infotiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
.infotiptext {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
background-color: ${(props) => props.theme.requestTabs.active.bg};
|
||||
@@ -62,7 +62,7 @@ const Wrapper = styled.div`
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tooltiptext::after {
|
||||
.infotiptext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import HttpMethodSelector from './HttpMethodSelector';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { IconDeviceFloppy, IconArrowRight } from '@tabler/icons';
|
||||
import { IconDeviceFloppy, IconArrowRight, IconCode } from '@tabler/icons';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GenerateCodeItem from 'components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/index';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const { theme, storedTheme } = useTheme();
|
||||
@@ -17,26 +19,43 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const url = item.draft ? get(item, 'draft.request.url', '') : get(item, 'request.url', '');
|
||||
const isMac = isMacOS();
|
||||
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
|
||||
const editorRef = useRef(null);
|
||||
|
||||
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
|
||||
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = document.querySelector('.method-selector-container');
|
||||
setMethodSelectorWidth(el.offsetWidth);
|
||||
}, [method]);
|
||||
|
||||
const onSave = () => {
|
||||
const onSave = (finalValue) => {
|
||||
dispatch(saveRequest(item.uid, collection.uid));
|
||||
};
|
||||
|
||||
const onUrlChange = (value) => {
|
||||
if (!editorRef.current?.editor) return;
|
||||
const editor = editorRef.current.editor;
|
||||
const cursor = editor.getCursor();
|
||||
|
||||
const finalUrl = value?.trim() ?? value;
|
||||
|
||||
dispatch(
|
||||
requestUrlChanged({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
url: value && typeof value === 'string' ? value.trim() : value
|
||||
url: finalUrl
|
||||
})
|
||||
);
|
||||
|
||||
// Restore cursor position only if URL was trimmed
|
||||
if (finalUrl !== value) {
|
||||
setTimeout(() => {
|
||||
if (editor) {
|
||||
editor.setCursor(cursor);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const onMethodSelect = (verb) => {
|
||||
@@ -49,6 +68,15 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleGenerateCode = (e) => {
|
||||
e.stopPropagation();
|
||||
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
|
||||
setGenerateCodeItemModalOpen(true);
|
||||
} else {
|
||||
toast.error('URL is required');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex items-center">
|
||||
<div className="flex items-center h-full method-selector-container">
|
||||
@@ -63,8 +91,9 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
}}
|
||||
>
|
||||
<SingleLineEditor
|
||||
ref={editorRef}
|
||||
value={url}
|
||||
onSave={onSave}
|
||||
onSave={(finalValue) => onSave(finalValue)}
|
||||
theme={storedTheme}
|
||||
onChange={(newValue) => onUrlChange(newValue)}
|
||||
onRun={handleRun}
|
||||
@@ -74,7 +103,23 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
||||
<div
|
||||
className="tooltip mx-3"
|
||||
className="infotip mr-3"
|
||||
onClick={(e) => {
|
||||
handleGenerateCode(e);
|
||||
}}
|
||||
>
|
||||
<IconCode
|
||||
color={theme.requestTabs.icon.color}
|
||||
strokeWidth={1.5}
|
||||
size={22}
|
||||
className={'cursor-pointer'}
|
||||
/>
|
||||
<span className="infotiptext text-xs">
|
||||
Generate Code
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="infotip mr-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!item.draft) return;
|
||||
@@ -87,13 +132,16 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
size={22}
|
||||
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
/>
|
||||
<span className="tooltiptext text-xs">
|
||||
<span className="infotiptext text-xs">
|
||||
Save <span className="shortcut">({saveShortcut})</span>
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
|
||||
</div>
|
||||
</div>
|
||||
{generateCodeItemModalOpen && (
|
||||
<GenerateCodeItem collection={collection} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
value={bodyContent[bodyMode] || ''}
|
||||
onEdit={onEdit}
|
||||
onRun={onRun}
|
||||
|
||||
@@ -47,6 +47,7 @@ const Script = ({ item, collection }) => {
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
@@ -60,6 +61,7 @@ const Script = ({ item, collection }) => {
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -34,6 +34,7 @@ const Tests = ({ item, collection }) => {
|
||||
value={tests || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from 'providers/Theme';
|
||||
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
@@ -83,14 +83,14 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
@@ -132,6 +132,7 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -2,4 +2,4 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div``;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
@@ -4,6 +4,7 @@ import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
|
||||
|
||||
@@ -51,14 +52,20 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<span className="mr-2">
|
||||
<JsSandboxMode collection={collection} />
|
||||
</span>
|
||||
<span className="mr-2">
|
||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||
<span className="mr-3">
|
||||
<ToolHint text="Runner" toolhintId="RunnnerToolhintId" place='bottom'>
|
||||
<IconRun className="cursor-pointer" size={18} strokeWidth={1.5} onClick={handleRun} />
|
||||
</ToolHint>
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
<ToolHint text="Variables" toolhintId="VariablesToolhintId">
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
</ToolHint>
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||
<ToolHint text="Collection Settings" toolhintId="CollectionSettingsToolhintId">
|
||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||
</ToolHint>
|
||||
</span>
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const CloseTabIcon = () => (
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseTabIcon;
|
||||
@@ -0,0 +1,15 @@
|
||||
const DraftTabIcon = () => (
|
||||
<svg
|
||||
focusable="false"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="8"
|
||||
height="16"
|
||||
fill="#cc7b1b"
|
||||
className="has-changes-icon"
|
||||
viewBox="0 0 8 8"
|
||||
>
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default DraftTabIcon;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
import CloseTabIcon from './CloseTabIcon';
|
||||
|
||||
const RequestTabNotFound = ({ handleCloseClick }) => {
|
||||
const [showErrorMessage, setShowErrorMessage] = useState(false);
|
||||
@@ -28,12 +29,7 @@ const RequestTabNotFound = ({ handleCloseClick }) => {
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
<CloseTabIcon />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import CloseTabIcon from './CloseTabIcon';
|
||||
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
|
||||
|
||||
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||
@@ -51,12 +52,7 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||
<>
|
||||
<div className="flex items-center tab-label pl-2">{getTabInfo(type, tabName)}</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
<CloseTabIcon />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,8 @@ import StyledWrapper from './StyledWrapper';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import CloneCollectionItem from 'components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index';
|
||||
import NewRequest from 'components/Sidebar/NewRequest/index';
|
||||
import CloseTabIcon from './CloseTabIcon';
|
||||
import DraftTabIcon from './DraftTabIcon';
|
||||
|
||||
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -49,9 +51,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
|
||||
const handleMouseUp = (e) => {
|
||||
if (e.button === 1) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Close the tab
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [tab.uid]
|
||||
@@ -68,7 +71,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
||||
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<StyledWrapper
|
||||
className="flex items-center justify-between tab-container px-1"
|
||||
onMouseUp={handleMouseUp} // Add middle-click behavior here
|
||||
>
|
||||
{tab.type === 'folder-settings' ? (
|
||||
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} tabName={folder?.name} />
|
||||
) : (
|
||||
@@ -82,7 +88,17 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
|
||||
if (!item) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<StyledWrapper
|
||||
className="flex items-center justify-between tab-container px-1"
|
||||
onMouseUp={(e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
dispatch(closeTabs({ tabUids: [tab.uid] }));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RequestTabNotFound handleCloseClick={handleCloseClick} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
@@ -166,24 +182,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
}}
|
||||
>
|
||||
{!item.draft ? (
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
<CloseTabIcon />
|
||||
) : (
|
||||
<svg
|
||||
focusable="false"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="8"
|
||||
height="16"
|
||||
fill="#cc7b1b"
|
||||
className="has-changes-icon"
|
||||
viewBox="0 0 8 8"
|
||||
>
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
<DraftTabIcon />
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IconFilter, IconX } from '@tabler/icons';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import { Tooltip as ReactInfotip } from 'react-tooltip';
|
||||
|
||||
const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
const inputRef = useRef(null);
|
||||
@@ -19,7 +19,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const tooltipText = useMemo(() => {
|
||||
const infotipText = useMemo(() => {
|
||||
if (mode.includes('json')) {
|
||||
return 'Filter with JSONPath';
|
||||
}
|
||||
@@ -49,7 +49,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2 pointer-events-none'
|
||||
}
|
||||
>
|
||||
{tooltipText && !isExpanded && <ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />}
|
||||
{infotipText && !isExpanded && <ReactInfotip anchorId={'request-filter-icon'} html={infotipText} />}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
|
||||
@@ -29,7 +29,7 @@ const QueryResultPreview = ({
|
||||
setNumPages(numPages);
|
||||
}
|
||||
// Fail safe, so we don't render anything with an invalid tab
|
||||
if (!allowedPreviewModes.includes(previewTab)) {
|
||||
if (!allowedPreviewModes.find((previewMode) => previewMode?.uid == previewTab?.uid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ const QueryResultPreview = ({
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
};
|
||||
|
||||
switch (previewTab) {
|
||||
switch (previewTab?.mode) {
|
||||
case 'preview-web': {
|
||||
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent?.url || ''}">`);
|
||||
return (
|
||||
@@ -81,6 +81,7 @@ const QueryResultPreview = ({
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
theme={displayedTheme}
|
||||
onRun={onRun}
|
||||
value={formattedData}
|
||||
|
||||
@@ -12,18 +12,35 @@ import { useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useTheme } from 'providers/Theme/index';
|
||||
import { uuid } from 'utils/common/index';
|
||||
|
||||
const formatResponse = (data, mode, filter) => {
|
||||
if (data === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (mode.includes('json')) {
|
||||
let isValidJSON = false;
|
||||
|
||||
try {
|
||||
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object';
|
||||
} catch (error) {
|
||||
console.log('Error parsing JSON: ', error.message);
|
||||
}
|
||||
|
||||
if (!isValidJSON && typeof data === 'string') {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
try {
|
||||
data = JSONPath({ path: filter, json: data });
|
||||
} catch (e) {
|
||||
console.warn('Could not filter with JSONPath.', e.message);
|
||||
console.warn('Could not apply JSONPath filter:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +52,6 @@ const formatResponse = (data, mode, filter) => {
|
||||
if (typeof parsed === 'string') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return safeStringifyJSON(parsed, true);
|
||||
}
|
||||
|
||||
@@ -43,7 +59,7 @@ const formatResponse = (data, mode, filter) => {
|
||||
return data;
|
||||
}
|
||||
|
||||
return safeStringifyJSON(data);
|
||||
return safeStringifyJSON(data, true);
|
||||
};
|
||||
|
||||
const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
|
||||
@@ -59,18 +75,18 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
|
||||
const allowedPreviewModes = useMemo(() => {
|
||||
// Always show raw
|
||||
const allowedPreviewModes = ['raw'];
|
||||
const allowedPreviewModes = [{ mode: 'raw', name: 'Raw', uid: uuid() }];
|
||||
|
||||
if (mode.includes('html') && typeof data === 'string') {
|
||||
allowedPreviewModes.unshift('preview-web');
|
||||
allowedPreviewModes.unshift({ mode: 'preview-web', name: 'Web', uid: uuid() });
|
||||
} else if (mode.includes('image')) {
|
||||
allowedPreviewModes.unshift('preview-image');
|
||||
allowedPreviewModes.unshift({ mode: 'preview-image', name: 'Image', uid: uuid() });
|
||||
} else if (contentType.includes('pdf')) {
|
||||
allowedPreviewModes.unshift('preview-pdf');
|
||||
allowedPreviewModes.unshift({ mode: 'preview-pdf', name: 'PDF', uid: uuid() });
|
||||
} else if (contentType.includes('audio')) {
|
||||
allowedPreviewModes.unshift('preview-audio');
|
||||
allowedPreviewModes.unshift({ mode: 'preview-audio', name: 'Audio', uid: uuid() });
|
||||
} else if (contentType.includes('video')) {
|
||||
allowedPreviewModes.unshift('preview-video');
|
||||
allowedPreviewModes.unshift({ mode: 'preview-video', name: 'Video', uid: uuid() });
|
||||
}
|
||||
|
||||
return allowedPreviewModes;
|
||||
@@ -79,7 +95,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
const [previewTab, setPreviewTab] = useState(allowedPreviewModes[0]);
|
||||
// Ensure the active Tab is always allowed
|
||||
useEffect(() => {
|
||||
if (!allowedPreviewModes.includes(previewTab)) {
|
||||
if (!allowedPreviewModes.find((previewMode) => previewMode?.uid == previewTab?.uid)) {
|
||||
setPreviewTab(allowedPreviewModes[0]);
|
||||
}
|
||||
}, [previewTab, allowedPreviewModes]);
|
||||
@@ -91,12 +107,15 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
|
||||
return allowedPreviewModes.map((previewMode) => (
|
||||
<div
|
||||
className={classnames('select-none capitalize', previewMode === previewTab ? 'active' : 'cursor-pointer')}
|
||||
className={classnames(
|
||||
'select-none capitalize',
|
||||
previewMode?.uid === previewTab?.uid ? 'active' : 'cursor-pointer'
|
||||
)}
|
||||
role="tab"
|
||||
onClick={() => setPreviewTab(previewMode)}
|
||||
key={previewMode}
|
||||
key={previewMode?.uid}
|
||||
>
|
||||
{previewMode.replace(/-(.*)/, ' ')}
|
||||
{previewMode?.name}
|
||||
</div>
|
||||
));
|
||||
}, [allowedPreviewModes, previewTab]);
|
||||
|
||||
@@ -17,7 +17,7 @@ const Timeline = ({ request, response }) => {
|
||||
});
|
||||
});
|
||||
|
||||
let requestData = safeStringifyJSON(request.data);
|
||||
let requestData = typeof request?.data === "string" ? request?.data : safeStringifyJSON(request?.data, true);
|
||||
|
||||
return (
|
||||
<StyledWrapper className="pb-4 w-full">
|
||||
@@ -35,7 +35,8 @@ const Timeline = ({ request, response }) => {
|
||||
|
||||
{requestData ? (
|
||||
<pre className="line request">
|
||||
<span className="arrow">{'>'}</span> data {requestData}
|
||||
<span className="arrow">{'>'}</span> data{' '}
|
||||
<pre className="text-sm flex flex-wrap whitespace-break-spaces">{requestData}</pre>
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as Yup from 'yup';
|
||||
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { cloneCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
const CloneCollection = ({ onClose, collection }) => {
|
||||
@@ -41,10 +41,10 @@ const CloneCollection = ({ onClose, collection }) => {
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
toast.success('Collection created');
|
||||
toast.success('Collection created!');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while creating the collection'));
|
||||
.catch((e) => toast.error('An error occurred while creating the collection - ' + e));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ const CloneCollection = ({ onClose, collection }) => {
|
||||
|
||||
return (
|
||||
<Modal size="sm" title="Clone Collection" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="collection-name" className="flex items-center font-semibold">
|
||||
Name
|
||||
@@ -126,9 +126,9 @@ const CloneCollection = ({ onClose, collection }) => {
|
||||
|
||||
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
|
||||
<span className="font-semibold">Folder Name</span>
|
||||
<Tooltip
|
||||
<InfoTip
|
||||
text="This folder will be created under the selected location"
|
||||
tooltipId="collection-folder-name-tooltip"
|
||||
infotipId="collection-folder-name-infotip"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -25,6 +25,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
onSubmit: (values) => {
|
||||
dispatch(cloneItem(values.name, item.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Request cloned!');
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -49,7 +50,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
{isFolder ? 'Folder' : 'Request'} Name
|
||||
|
||||
@@ -53,6 +53,7 @@ const CodeView = ({ language, item }) => {
|
||||
collection={collection}
|
||||
value={snippet}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
theme={displayedTheme}
|
||||
mode={lang}
|
||||
/>
|
||||
|
||||
@@ -8,8 +8,9 @@ const StyledWrapper = styled.div`
|
||||
.generate-code-sidebar {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||
min-height: 400px;
|
||||
max-height: 80vh;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.generate-code-item {
|
||||
|
||||
@@ -6,56 +6,11 @@ import { isValidUrl } from 'utils/url';
|
||||
import { find, get } from 'lodash';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
import { interpolateUrl, interpolateUrlPathParams } from 'utils/url/index';
|
||||
|
||||
const languages = [
|
||||
{
|
||||
name: 'HTTP',
|
||||
target: 'http',
|
||||
client: 'http1.1'
|
||||
},
|
||||
{
|
||||
name: 'JavaScript-Fetch',
|
||||
target: 'javascript',
|
||||
client: 'fetch'
|
||||
},
|
||||
{
|
||||
name: 'Javascript-jQuery',
|
||||
target: 'javascript',
|
||||
client: 'jquery'
|
||||
},
|
||||
{
|
||||
name: 'Javascript-axios',
|
||||
target: 'javascript',
|
||||
client: 'axios'
|
||||
},
|
||||
{
|
||||
name: 'Python-Python3',
|
||||
target: 'python',
|
||||
client: 'python3'
|
||||
},
|
||||
{
|
||||
name: 'Python-Requests',
|
||||
target: 'python',
|
||||
client: 'requests'
|
||||
},
|
||||
{
|
||||
name: 'PHP',
|
||||
target: 'php',
|
||||
client: 'curl'
|
||||
},
|
||||
{
|
||||
name: 'Shell-curl',
|
||||
target: 'shell',
|
||||
client: 'curl'
|
||||
},
|
||||
{
|
||||
name: 'Shell-httpie',
|
||||
target: 'shell',
|
||||
client: 'httpie'
|
||||
}
|
||||
];
|
||||
import { getLanguages } from 'utils/codegenerator/targets';
|
||||
|
||||
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
||||
const languages = getLanguages();
|
||||
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
let envVars = {};
|
||||
if (environment) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Modal from 'components/Modal';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { isItemAFolder } from 'utils/tabs';
|
||||
import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -27,8 +28,14 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||
if (!isFolder && item.draft) {
|
||||
await dispatch(saveRequest(item.uid, collection.uid, true));
|
||||
}
|
||||
dispatch(renameItem(values.name, item.uid, collection.uid));
|
||||
onClose();
|
||||
dispatch(renameItem(values.name, item.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Request renamed!');
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err ? err.message : 'An error occurred while renaming the request');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,7 +55,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
{isFolder ? 'Folder' : 'Request'} Name
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user