Merge branch 'main' into docs/#735-spanish-translation

This commit is contained in:
Pedro Andrés Chaparro Quintero 2023-10-26 09:18:30 -05:00
commit 769e48706d
No known key found for this signature in database
GPG Key ID: 1595DE7F6024261E
47 changed files with 519 additions and 64 deletions

View File

@ -1,12 +1,12 @@
**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Español](/contributing_es.md)
**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [Español](/contributing_es.md)
## Lets make bruno better, together !!
## Let's make bruno better, together !!
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
### Technology Stack
Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections)
Bruno is built using Next.js and React. We also use electron to ship a desktop version (that supports local collections)
Libraries we use
@ -23,7 +23,7 @@ Libraries we use
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
### Lets start coding
### Let's start coding
Please reference [development.md](docs/development.md) for instructions on running the local development environment.

View File

@ -1,4 +1,4 @@
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | **Deutsch** | [Español](/contributing_es.md)
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | **Deutsch** | [Français](/contributing_fr.md) | [Español](/contributing_es.md)
## Lass uns Bruno noch besser machen, gemeinsam !!
@ -6,7 +6,7 @@ Ich freue mich, dass Du Bruno verbessern möchtest. Hier findest Du eine Anleitu
### Technologie Stack
Bruno ist mit NextJs und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt).
Bruno ist mit Next.js und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt).
Bibliotheken die wir benutzen

View File

@ -1,4 +1,4 @@
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | **Español**
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | **Español**
## ¡Juntos, hagamos a Bruno mejor!

37
contributing_fr.md Normal file
View File

@ -0,0 +1,37 @@
[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | **Français** | [Español](/contributing_es.md)
## Ensemble, améliorons Bruno !
Je suis content de voir que vous envisagez améliorer Bruno. Ci-dessous, vous trouverez les règles et guides pour récupérer Bruno sur votre ordinateur.
### Technologies utilisées
Bruno est construit en utilisant NextJs et React. Nous utilisons aussi Electron pour embarquer la version ordinateur (qui permet les collections locales).
Les bibliothèques que nous utilisons :
- CSS - Tailwind
- Code Editors - Codemirror
- State Management - Redux
- Icons - Tabler Icons
- Forms - formik
- Schema Validation - Yup
- Request Client - axios
- Filesystem Watcher - chokidar
### Dépendances
Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
### Commençons à coder
Veuillez vous référez à la [documentation de développement](docs/development_fr.md) pour les instructions de démarrage de l'environnement de développement local.
### Ouvrir une Pull Request
- Merci de conserver les PR petites et focalisées sur un seul objectif
- Merci de suivre le format de nom des branches
- feature/[feature name]: Cette branche devrait contenir une fonctionnalité spécifique
- Exemple: feature/dark-mode
- bugfix/[bug name]: Cette branche devrait contenir seulement une solution pour pour une bogue spécifique
- Exemple: bugfix/bug-1

View File

@ -1,4 +1,4 @@
[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Español](/contributing_es.md)
[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [Español](/contributing_es.md)
## Давайте вместе сделаем Бруно лучше!!!
@ -6,7 +6,7 @@
### Стек
Bruno построен с использованием NextJs и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
Bruno построен с использованием Next.js и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
Библиотеки, которые мы используем

View File

@ -1,4 +1,4 @@
[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** | [Deutsch](/contributing_de.md) | [Español](/contributing_es.md)
[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [Español](/contributing_es.md)
## Bruno'yu birlikte daha iyi hale getirelim !!
@ -6,7 +6,7 @@ Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilg
### Kullanılan Teknolojiler
Bruno, NextJs ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz
Bruno, Next.js ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz
Kullandığımız kütüphaneler

View File

@ -1,4 +1,4 @@
[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Español](/contributing_es.md)
[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [Español](/contributing_es.md)
## Давайте зробимо Bruno краще, разом !!
@ -6,7 +6,7 @@
### Стек технологій
Bruno побудований на NextJs та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron
Bruno побудований на Next.js та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron
Бібліотеки, які ми використовуємо

View File

@ -1,8 +1,8 @@
**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | [Español](/docs/development_es.md)
**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | [Français](/docs/development_fr.md) | [Español](/docs/development_es.md)
## Development
Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
Bruno is being developed as a desktop app. You need to load the app by running the Next.js app in one terminal and then run the electron app in another terminal.
### Dependencies

View File

@ -1,8 +1,8 @@
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | **Deutsch** | [Español](/docs/development_es.md)
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | **Deutsch** | [Français](/docs/development_fr.md) | [Español](/docs/development_es.md)
## Entwicklung
Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die NextJs-App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App.
Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die Next.js App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App.
### Abhängigkeiten

View File

@ -1,4 +1,4 @@
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | **Español**
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | [Français](/docs/development_fr.md) | **Español**
## Desarrollo

55
docs/development_fr.md Normal file
View File

@ -0,0 +1,55 @@
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | **Français** | [Español](/docs/development_es.md)
## Développement
Bruno est développé comme une application de _lourde_. Vous devez charger l'application en démarrant nextjs dans un terminal, puis démarre l'application Electron dans un autre terminal.
### Dépendances
- NodeJS v18
### Développement local
```bash
# use nodejs 18 version
nvm use
# install deps
npm i --legacy-peer-deps
# build graphql docs
npm run build:graphql-docs
# build bruno query
npm run build:bruno-query
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
### Dépannage
Vous pourriez rencontrer une error `Unsupported platform` pendant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules`, le fichier `package-lock.json` et lancer à nouveau `npm install`. Cela devrait isntaller tous les paquets nécessaires pour lancer l'application.
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Tests
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

@ -1,8 +1,8 @@
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский** | [Deutsch](/docs/development_de.md) | [Español](/docs/development_es.md)
[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский** | [Deutsch](/docs/development_de.md) | [Français](/docs/development_fr.md) | [Español](/docs/development_es.md)
## Разработка
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение electron в другом терминале.
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение Next.js в одном терминале, а затем запустить приложение electron в другом терминале.
### Зависимости

View File

@ -1,8 +1,8 @@
[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | [Español](/docs/development_es.md)
[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md) | [Deutsch](/docs/development_de.md) | [Français](/docs/development_fr.md) | [Español](/docs/development_es.md)
## Розробка
Bruno розробляється як декстопний застосунок. Вам потрібно запустити nextjs в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу.
Bruno розробляється як декстопний застосунок. Вам потрібно запустити Next.js в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу.
### Залежності

6
package-lock.json generated
View File

@ -16630,7 +16630,7 @@
},
"packages/bruno-electron": {
"name": "bruno",
"version": "v0.27.1",
"version": "v0.27.2",
"dependencies": {
"@aws-sdk/credential-providers": "^3.425.0",
"@usebruno/js": "0.9.1",
@ -16641,6 +16641,7 @@
"axios": "^1.5.1",
"chai": "^4.3.7",
"chokidar": "^3.5.3",
"content-disposition": "^0.5.4",
"decomment": "^0.9.5",
"dotenv": "^16.0.3",
"electron-is-dev": "^2.0.0",
@ -16656,6 +16657,7 @@
"is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"nanoid": "3.3.4",
"node-machine-id": "^1.1.12",
@ -21519,6 +21521,7 @@
"axios": "^1.5.1",
"chai": "^4.3.7",
"chokidar": "^3.5.3",
"content-disposition": "^0.5.4",
"decomment": "^0.9.5",
"dmg-license": "^1.0.11",
"dotenv": "^16.0.3",
@ -21538,6 +21541,7 @@
"is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"nanoid": "3.3.4",
"node-machine-id": "^1.1.12",

View File

@ -3,7 +3,7 @@
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "cross-env ENV=dev next dev",
"dev": "cross-env ENV=dev next dev -p 3000",
"build": "next build && next export",
"start": "next start",
"lint": "next lint",

View File

@ -70,6 +70,35 @@ export default class CodeEditor extends React.Component {
'Ctrl-F': 'findPersistent',
Tab: function (cm) {
cm.replaceSelection(' ', 'end');
},
'Ctrl-Y': 'foldAll',
'Cmd-Y': 'foldAll',
'Ctrl-I': 'unfoldAll',
'Cmd-I': 'unfoldAll'
},
foldOptions: {
widget: (from, to) => {
var count = undefined;
var internal = this.editor.getRange(from, to);
if (this.props.mode == 'application/ld+json') {
if (this.editor.getLine(from.line).endsWith('[')) {
var toParse = '[' + internal + ']';
} else var toParse = '{' + internal + '}';
try {
count = Object.keys(JSON.parse(toParse)).length;
} catch (e) {}
} else if (this.props.mode == 'application/xml') {
var doc = new DOMParser();
try {
//add header element and remove prefix namespaces for DOMParser
var dcm = doc.parseFromString(
'<a> ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '</a>',
'application/xml'
);
count = dcm.documentElement.children.length;
} catch (e) {}
}
return count ? `\u21A4${count}\u21A6` : '\u2194';
}
}
}));

View File

@ -10,6 +10,7 @@ const StyledWrapper = styled.div`
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;
height: 100%;
max-height: 85vh;
overflow-y: auto;
}
@ -44,10 +45,8 @@ const StyledWrapper = styled.div`
border-bottom: none;
color: ${(props) => props.theme.textLink};
&:hover {
span {
text-decoration: underline;
}
span:hover {
text-decoration: underline;
}
}

View File

@ -1,12 +1,11 @@
import React, { useEffect, useState, forwardRef, useRef } from 'react';
import { findEnvironmentInCollection } from 'utils/collections';
import toast from 'react-hot-toast';
import { toastError } from 'utils/common/error';
import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from '../CreateEnvironment';
import { IconDownload } from '@tabler/icons';
import { IconDownload, IconShieldLock } from '@tabler/icons';
import ImportEnvironment from '../ImportEnvironment';
import ManageSecrets from '../ManageSecrets';
import StyledWrapper from './StyledWrapper';
const EnvironmentList = ({ collection }) => {
@ -14,6 +13,7 @@ const EnvironmentList = ({ collection }) => {
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false);
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
@ -54,6 +54,7 @@ const EnvironmentList = ({ collection }) => {
<StyledWrapper>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
<div className="flex">
<div>
<div className="environments-sidebar flex flex-col">
@ -72,9 +73,15 @@ const EnvironmentList = ({ collection }) => {
+ <span>Create</span>
</div>
<div className="mt-auto flex items-center btn-import-environment" onClick={() => setOpenImportModal(true)}>
<IconDownload size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Import</span>
<div className="mt-auto btn-import-environment">
<div className="flex items-center" onClick={() => setOpenImportModal(true)}>
<IconDownload size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Import</span>
</div>
<div className="flex items-center mt-2" onClick={() => setOpenManageSecretsModal(true)}>
<IconShieldLock size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Managing Secrets</span>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,31 @@
import React from 'react';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
const ManageSecrets = ({ onClose }) => {
return (
<Portal>
<Modal size="sm" title="Manage Secrets" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<p>In any collection, there are secrets that need to be managed.</p>
<p className="mt-2">These secrets can be anything such as API keys, passwords, or tokens.</p>
<p className="mt-4">Bruno offers two approaches to manage secrets in collections.</p>
<p className="mt-2">
Read more about it in our{' '}
<a
href="https://docs.usebruno.com/secrets-management/overview.html"
target="_blank"
rel="noreferrer"
className="text-link hover:underline"
>
docs
</a>
.
</p>
</div>
</Modal>
</Portal>
);
};
export default ManageSecrets;

View File

@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.yellow};
}
}
.muted {
color: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

View File

@ -85,7 +85,16 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
{tabs}
</div>
{error ? (
<span className="text-red-500">{error}</span>
<div>
<div className="text-red-500">{error}</div>
{error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? (
<div className="mt-6 muted text-xs">
You can disable SSL verification in the Preferences. <br />
To open the Preferences, click on the gear icon in the bottom left corner.
</div>
) : null}
</div>
) : (
<QueryResultPreview
previewTab={previewTab}

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
font-size: 0.8125rem;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
`;
export default StyledWrapper;

View File

@ -0,0 +1,32 @@
import React from 'react';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import get from 'lodash/get';
import { IconDownload } from '@tabler/icons';
const ResponseSave = ({ item }) => {
const { ipcRenderer } = window;
const response = item.response || {};
const saveResponseToFile = () => {
return new Promise((resolve, reject) => {
console.log(item);
ipcRenderer
.invoke('renderer:save-response-to-file', response, item.requestSent.url)
.then(resolve)
.catch((err) => {
toast.error(get(err, 'error.message') || 'Something went wrong!');
reject(err);
});
});
};
return (
<StyledWrapper className="ml-4 flex items-center">
<button onClick={saveResponseToFile} disabled={!response.dataBuffer} title="Save response to file">
<IconDownload size={16} strokeWidth={1.5} />
</button>
</StyledWrapper>
);
};
export default ResponseSave;

View File

@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper';
// Todo: text-error class is not getting pulled in for 500 errors
const StatusCode = ({ status }) => {
const getTabClassname = (status) => {
return classnames('', {
return classnames('ml-2', {
'text-ok': status >= 100 && status < 200,
'text-ok': status >= 200 && status < 300,
'text-error': status >= 300 && status < 400,

View File

@ -14,6 +14,7 @@ import Timeline from './Timeline';
import TestResults from './TestResults';
import TestResultsLabel from './TestResultsLabel';
import StyledWrapper from './StyledWrapper';
import ResponseSave from 'src/components/ResponsePane/ResponseSave';
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const dispatch = useDispatch();
@ -112,6 +113,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
</div>
{!isLoading ? (
<div className="flex flex-grow justify-end items-center">
<ResponseSave item={item} />
<StatusCode status={response.status} />
<ResponseTime duration={response.duration} />
<ResponseSize size={response.size} />

View File

@ -7,7 +7,7 @@ import Preferences from 'components/Preferences';
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IconSettings } from '@tabler/icons';
import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app';
import { updateLeftSidebarWidth, updateIsDragging, showPreferences } from 'providers/ReduxStore/slices/app';
import { useTheme } from 'providers/Theme';
const MIN_LEFT_SIDEBAR_WIDTH = 222;
@ -15,7 +15,7 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600;
const Sidebar = () => {
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
const [preferencesOpen, setPreferencesOpen] = useState(false);
const preferencesOpen = useSelector((state) => state.app.showPreferences);
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
@ -78,7 +78,7 @@ const Sidebar = () => {
<StyledWrapper className="flex relative h-screen">
<aside>
<div className="flex flex-row h-screen w-full">
{preferencesOpen && <Preferences onClose={() => setPreferencesOpen(false)} />}
{preferencesOpen && <Preferences onClose={() => dispatch(showPreferences(false))} />}
<div className="flex flex-col w-full" style={{ width: asideWidth }}>
<div className="flex flex-col flex-grow">
@ -92,7 +92,7 @@ const Sidebar = () => {
size={18}
strokeWidth={1.5}
className="mr-2 hover:text-gray-700"
onClick={() => setPreferencesOpen(true)}
onClick={() => dispatch(showPreferences(true))}
/>
</div>
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>

View File

@ -21,6 +21,7 @@ if (!SERVER_RENDERED) {
require('codemirror/addon/edit/matchbrackets');
require('codemirror/addon/fold/brace-fold');
require('codemirror/addon/fold/foldgutter');
require('codemirror/addon/fold/xml-fold');
require('codemirror/addon/hint/show-hint');
require('codemirror/addon/lint/lint');
require('codemirror/addon/mode/overlay');

View File

@ -41,6 +41,19 @@ function MyApp({ Component, pageProps }) {
return null;
}
if (!window.ipcRenderer) {
return (
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mx-10 my-10 rounded relative" role="alert">
<strong class="font-bold">ERROR:</strong>
<span className="block inline ml-1">"ipcRenderer" not found in window object.</span>
<div>
You most likely opened Bruno inside your web browser. Bruno only works within Electron, you can start Electron
in an adjacent terminal using "npm run dev:electron".
</div>
</div>
);
}
return (
<ErrorBoundary>
<SafeHydrate>

View File

@ -14,7 +14,7 @@ import {
runFolderEvent,
brunoConfigUpdateEvent
} from 'providers/ReduxStore/slices/collections';
import { updatePreferences } from 'providers/ReduxStore/slices/app';
import { showPreferences, updatePreferences } from 'providers/ReduxStore/slices/app';
import toast from 'react-hot-toast';
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
import { isElectron } from 'utils/common/platform';
@ -127,6 +127,10 @@ const useIpcEvents = () => {
dispatch(brunoConfigUpdateEvent(val))
);
const showPreferencesListener = ipcRenderer.on('main:open-preferences', () => {
dispatch(showPreferences(true));
});
const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => {
dispatch(updatePreferences(val));
});
@ -143,6 +147,7 @@ const useIpcEvents = () => {
removeProcessEnvUpdatesListener();
removeConsoleLogListener();
removeConfigUpdatesListener();
showPreferencesListener();
removePreferencesUpdatesListener();
};
}, [isElectron]);

View File

@ -7,6 +7,7 @@ const initialState = {
leftSidebarWidth: 222,
screenWidth: 500,
showHomePage: false,
showPreferences: false,
preferences: {
request: {
sslVerification: true,
@ -40,6 +41,9 @@ export const appSlice = createSlice({
hideHomePage: (state) => {
state.showHomePage = false;
},
showPreferences: (state, action) => {
state.showPreferences = action.payload;
},
updatePreferences: (state, action) => {
state.preferences = action.payload;
}
@ -53,6 +57,7 @@ export const {
updateIsDragging,
showHomePage,
hideHomePage,
showPreferences,
updatePreferences
} = appSlice.actions;

View File

@ -328,7 +328,9 @@ const parseOpenApiCollection = (data) => {
.map(([path, methods]) => {
return Object.entries(methods)
.filter(([method, op]) => {
['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(method.toLowerCase());
return ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(
method.toLowerCase()
);
})
.map(([method, operationObject]) => {
return {

View File

@ -68,8 +68,8 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
if (!brunoRequestItem.request.script) {
brunoRequestItem.request.script = {};
}
if (Array.isArray(event.script.exec[0])) {
brunoRequestItem.request.script.req = event.script.exec[0].map((line) => `// ${line}`).join('\n');
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n');
} else {
brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `;
}
@ -78,8 +78,8 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
if (!brunoRequestItem.request.tests) {
brunoRequestItem.request.tests = {};
}
if (Array.isArray(event.script.exec[0])) {
brunoRequestItem.request.tests = event.script.exec[0].map((line) => `// ${line}`).join('\n');
if (Array.isArray(event.script.exec)) {
brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n');
} else {
brunoRequestItem.request.tests = `// ${event.script.exec[0]} `;
}

View File

@ -28,6 +28,7 @@
"axios": "^1.5.1",
"chai": "^4.3.7",
"chokidar": "^3.5.3",
"content-disposition": "^0.5.4",
"decomment": "^0.9.5",
"dotenv": "^16.0.3",
"electron-is-dev": "^2.0.0",
@ -43,6 +44,7 @@
"is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"nanoid": "3.3.4",
"node-machine-id": "^1.1.12",

View File

@ -12,6 +12,14 @@ const template = [
ipcMain.emit('main:open-collection');
}
},
{
label: 'Preferences',
accelerator: 'CommandOrControl+,',
click() {
ipcMain.emit('main:open-preferences');
}
},
{ type: 'separator' },
{ role: 'quit' }
]
},

View File

@ -67,7 +67,21 @@ app.on('ready', async () => {
slashes: true
});
mainWindow.loadURL(url);
mainWindow.loadURL(url).catch((reason) => {
console.error(`Error: Failed to load URL: "${url}" (Electron shows a blank screen because of this).`);
console.error('Original message:', reason);
if (isDev) {
console.error(
'Could not connect to Next.Js dev server, is it running?' +
' Start the dev server using "npm run dev:web" and restart electron'
);
} else {
console.error(
'If you are using an official production build: the above error is most likely a bug! ' +
' Please report this under: https://github.com/usebruno/bruno/issues'
);
}
});
watcher = new Watcher();
const handleBoundsChange = () => {

View File

@ -3,9 +3,12 @@ const fs = require('fs');
const qs = require('qs');
const https = require('https');
const axios = require('axios');
const path = require('path');
const decomment = require('decomment');
const Mustache = require('mustache');
const FormData = require('form-data');
const contentDispositionParser = require('content-disposition');
const mime = require('mime-types');
const { ipcMain } = require('electron');
const { forOwn, extend, each, get, compact } = require('lodash');
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
@ -24,6 +27,7 @@ const { SocksProxyAgent } = require('socks-proxy-agent');
const { makeAxiosInstance } = require('./axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
// override the default escape function to prevent escaping
Mustache.escape = function (value) {
@ -83,7 +87,14 @@ const getSize = (data) => {
return 0;
};
const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => {
const configureRequest = async (
collectionUid,
request,
envVars,
collectionVariables,
processEnvVars,
collectionPath
) => {
const httpsAgentRequestFields = {};
if (!preferencesUtil.shouldVerifyTls()) {
httpsAgentRequestFields['rejectUnauthorized'] = false;
@ -100,18 +111,29 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria
const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []);
for (let clientCert of clientCertConfig) {
const domain = interpolateString(clientCert.domain, interpolationOptions);
const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
let certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions);
certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath);
let keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions);
keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath);
if (domain && certFilePath && keyFilePath) {
const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*');
if (request.url.match(hostRegex)) {
try {
httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath);
} catch (err) {
console.log('Error reading cert file', err);
}
try {
httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath);
} catch (err) {
console.log('Error reading cert/key file', err);
console.log('Error reading key file', err);
}
httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions);
break;
}
@ -389,7 +411,8 @@ const registerNetworkIpc = (mainWindow) => {
request,
envVars,
collectionVariables,
processEnvVars
processEnvVars,
collectionPath
);
let response, responseTime;
@ -564,7 +587,8 @@ const registerNetworkIpc = (mainWindow) => {
preparedRequest,
envVars,
collection.collectionVariables,
processEnvVars
processEnvVars,
collectionPath
);
const response = await axiosInstance(preparedRequest);
@ -695,7 +719,8 @@ const registerNetworkIpc = (mainWindow) => {
request,
envVars,
collectionVariables,
processEnvVars
processEnvVars,
collectionPath
);
timeStart = Date.now();
@ -838,6 +863,51 @@ const registerNetworkIpc = (mainWindow) => {
}
}
);
// save response to file
ipcMain.handle('renderer:save-response-to-file', async (event, response, url) => {
try {
const getHeaderValue = (headerName) => {
if (response.headers) {
const header = response.headers.find((header) => header[0] === headerName);
if (header && header.length > 1) {
return header[1];
}
}
};
const getFileNameFromContentDispositionHeader = () => {
const contentDisposition = getHeaderValue('content-disposition');
try {
const disposition = contentDispositionParser.parse(contentDisposition);
return disposition && disposition.parameters['filename'];
} catch (error) {}
};
const getFileNameFromUrlPath = () => {
const lastPathLevel = new URL(url).pathname.split('/').pop();
if (lastPathLevel && /\..+/.exec(lastPathLevel)) {
return lastPathLevel;
}
};
const getFileNameBasedOnContentTypeHeader = () => {
const contentType = getHeaderValue('content-type');
const extension = (contentType && mime.extension(contentType)) || 'txt';
return `response.${extension}`;
};
const fileName =
getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader();
const filePath = await chooseFileToSave(mainWindow, fileName);
if (filePath) {
await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, 'base64'));
}
} catch (error) {
return Promise.reject(error);
}
});
};
module.exports = registerNetworkIpc;

View File

@ -23,6 +23,10 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
}
});
ipcMain.on('main:open-preferences', () => {
mainWindow.webContents.send('main:open-preferences');
});
ipcMain.handle('renderer:save-preferences', async (event, preferences) => {
try {
await savePreferences(preferences);

View File

@ -60,6 +60,14 @@ const writeFile = async (pathname, content) => {
}
};
const writeBinaryFile = async (pathname, content) => {
try {
fs.writeFileSync(pathname, content);
} catch (err) {
return Promise.reject(err);
}
};
const hasJsonExtension = (filename) => {
if (!filename || typeof filename !== 'string') return false;
return ['json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`));
@ -95,6 +103,14 @@ const browseDirectory = async (win) => {
return isDirectory(resolvedPath) ? resolvedPath : false;
};
const chooseFileToSave = async (win, preferredFileName = '') => {
const { filePath } = await dialog.showSaveDialog(win, {
defaultPath: preferredFileName
});
return filePath;
};
const searchForFiles = (dir, extension) => {
let results = [];
const files = fs.readdirSync(dir);
@ -126,10 +142,12 @@ module.exports = {
isDirectory,
normalizeAndResolvePath,
writeFile,
writeBinaryFile,
hasJsonExtension,
hasBruExtension,
createDirectory,
browseDirectory,
chooseFileToSave,
searchForFiles,
searchForBruFiles,
sanitizeDirectoryName

View File

@ -7,7 +7,7 @@ const devServer = async (dir, port) => {
// Build the renderer code and watch the files
await next.prepare();
// NextJS Server
// Next.js Server
const server = createServer(requestHandler);
server.listen(port || 8000, () => {

View File

@ -1,6 +1,6 @@
### Publishing Bruno to a new package manager
While our code is open source and available for everyone to use, we kindly request that you reach out to us before considering publication on new package managers. As the creator of Bruno, I hold the trademark `Bruno` for this project and would like to manage its distribution. If you'd like to see Bruno on a new package manager, please raise a Github issue.
While our code is open source and available for everyone to use, we kindly request that you reach out to us before considering publication on new package managers. As the creator of Bruno, I hold the trademark `Bruno` for this project and would like to manage its distribution. If you'd like to see Bruno on a new package manager, please raise a GitHub issue.
While, majority of our features are free and open source (which covers REST and GraphQL Apis)
We strive to strike a harmonious balance between open-source principles and sustainability - https://github.com/usebruno/bruno/discussions/269
While the majority of our features are free and open source (which covers REST and GraphQL Apis),
we strive to strike a harmonious balance between open-source principles and sustainability - https://github.com/usebruno/bruno/discussions/269

View File

@ -10,7 +10,7 @@
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Español](/readme_es.md)
**English** | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [Español](/readme_es.md)
Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there.

View File

@ -10,7 +10,7 @@
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | **Deutsch** | [Español](/readme_es.md)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | **Deutsch** | [Français](/readme_fr.md) | [Español](/readme_es.md)
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.

View File

@ -10,7 +10,7 @@
[![Sitio Web](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Descargas](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | **Español**
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | **Español**
Bruno un cliente de APIs nuevo e innovador, creado con el objetivo de revolucionar el panorama actual representado por Postman y otras herramientas similares.

96
readme_fr.md Normal file
View File

@ -0,0 +1,96 @@
<br />
<img src="assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE Opensource pour explorer et tester des APIs.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | **Français** | [Español](/readme_es.md)
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _status quo_ que représente Postman et les autres outils.
Bruno sauvegarde vos collections directement sur votre système de fichiers. Nous utilisons un langage de balise de type texte pour décrire les requêtes API.
Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler de manière collaborative sur vos collections d'APIs.
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
![bruno](assets/images/landing-2.png) <br /><br />
### Fonctionne sur de multiples platformes 🖥️
![bruno](assets/images/run-anywhere.png) <br /><br />
### Collaborer via Git 👩‍💻🧑‍💻
Ou n'importe quel système de gestion de sources
![bruno](assets/images/version-control.png) <br /><br />
### Liens importants 📌
- [Notre vision à long terme (en anglais)](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
- [Documentation](https://docs.usebruno.com)
- [Site web](https://www.usebruno.com)
- [Prix](https://www.usebruno.com/pricing)
- [Téléchargement](https://www.usebruno.com/downloads)
### Showcase 🎥
- [Témoignages](https://github.com/usebruno/bruno/discussions/343)
- [Centre de connaissance](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Soutien ❤️
Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !!
### Partage de témoignages 📣
Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion Github dédiée](https://github.com/usebruno/bruno/discussions/343)
### Publier Bruno sur un nouveau gestionnaire de paquets
Veuillez regarder [ici](publishing.md) pour plus d'information.
### Contribuer 👩‍💻🧑‍💻
Je suis heureux de voir que vous cherchez à améliorer Bruno. Merci de consulter le [guide de contribution](contributing_fr.md)
Même si vous n'êtes pas en mesure de contribuer directement via du code, n'hésitez pas à consigner les bogues et les demandes de nouvelles fonctionnalités pour résoudre vos cas d'usage !
### Auteurs
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### Restons en contact 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Marque
**Nom**
`Bruno` est une marque appartenant à [Anoop M D](https://www.helloanoop.com/)
**Logo**
Le logo est issu de [OpenMoji](https://openmoji.org/library/emoji-1F436/).
Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### Licence 📄
[MIT](license.md)

View File

@ -10,7 +10,7 @@
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Español](/readme_es.md)
[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [Español](/readme_es.md)
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.

View File

@ -10,7 +10,7 @@
[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![İndir](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | **Türkçe** | [Deutsch](/readme_de.md) | [Español](/readme_es.md)
[English](/readme.md) | [Українська](/readme_ua.md) | [Русский](/readme_ru.md) | **Türkçe** | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [Español](/readme_es.md)
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.

View File

@ -10,7 +10,7 @@
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Español](/readme_es.md)
[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [Español](/readme_es.md)
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.