From d6628d960e7679e7c5c2b4aea2447ad5d5589f02 Mon Sep 17 00:00:00 2001 From: nyyu Date: Fri, 13 Oct 2023 22:30:01 +0200 Subject: [PATCH 01/12] feat: support client certificates --- .../ClientCertSettings/StyledWrapper.js | 31 +++++ .../ClientCertSettings/index.js | 120 ++++++++++++++++++ .../components/CollectionSettings/index.js | 37 ++++++ .../bruno-electron/src/ipc/network/index.js | 39 ++++-- 4 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js new file mode 100644 index 000000000..bc5ad1565 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .settings-label { + width: 80px; + } + + input { + width: 300px; + } + + .textbox { + border: 1px solid #ccc; + padding: 0.15rem 0.45rem; + box-shadow: none; + border-radius: 0px; + outline: none; + box-shadow: none; + transition: border-color ease-in-out 0.1s; + border-radius: 3px; + background-color: ${(props) => props.theme.modal.input.bg}; + border: 1px solid ${(props) => props.theme.modal.input.border}; + + &:focus { + border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important; + outline: none !important; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js new file mode 100644 index 000000000..280e0f4bb --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js @@ -0,0 +1,120 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import StyledWrapper from './StyledWrapper'; + +const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => { + const formik = useFormik({ + initialValues: { + domain: '', + certFilePath: '', + keyFilePath: '', + passphrase: '' + }, + validationSchema: Yup.object({ + domain: Yup.string().required(), + certFilePath: Yup.string().required(), + keyFilePath: Yup.string().required(), + passphrase: Yup.string() + }), + onSubmit: (values) => { + onUpdate(values); + } + }); + + const getFile = (e) => { + formik.values[e.name] = e.files[0].path; + }; + + return ( + +

Current client certificates

+ +

New client certicate

+
+
+ + + {formik.touched.domain && formik.errors.domain ? ( +
{formik.errors.domain}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.certFilePath && formik.errors.certFilePath ? ( +
{formik.errors.certFilePath}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.keyFilePath && formik.errors.keyFilePath ? ( +
{formik.errors.keyFilePath}
+ ) : null} +
+
+ + + {formik.touched.passphrase && formik.errors.passphrase ? ( +
{formik.errors.passphrase}
+ ) : null} +
+
+ +
+
+
+ ); +}; + +export default ClientCertSettings; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index b3a1ece81..90b3af9a4 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -7,6 +7,7 @@ import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actio import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; import { useDispatch } from 'react-redux'; import ProxySettings from './ProxySettings'; +import ClientCertSettings from './ClientCertSettings'; import Headers from './Headers'; import Auth from './Auth'; import Script from './Script'; @@ -28,6 +29,8 @@ const CollectionSettings = ({ collection }) => { const proxyConfig = get(collection, 'brunoConfig.proxy', {}); + const clientCertConfig = get(collection, 'brunoConfig.clientCertificates', []); + const onProxySettingsUpdate = (config) => { const brunoConfig = cloneDeep(collection.brunoConfig); brunoConfig.proxy = config; @@ -38,6 +41,28 @@ const CollectionSettings = ({ collection }) => { .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); }; + const onClientCertSettingsUpdate = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates + ? brunoConfig.clientCertificates.push(config) + : (brunoConfig.clientCertificates = [config]); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + + const onClientCertSettingsRemove = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates = brunoConfig.clientCertificates.filter((item) => item.domain != config.domain); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + const getTabPanel = (tab) => { switch (tab) { case 'headers': { @@ -55,6 +80,15 @@ const CollectionSettings = ({ collection }) => { case 'proxy': { return ; } + case 'clientCert': { + return ( + + ); + } case 'docs': { return ; } @@ -85,6 +119,9 @@ const CollectionSettings = ({ collection }) => {
setTab('proxy')}> Proxy
+
setTab('clientCert')}> + Client certificate +
setTab('docs')}> Docs
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 8e8cb6247..9d308faf7 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1,4 +1,5 @@ const os = require('os'); +const fs = require('fs'); const qs = require('qs'); const https = require('https'); const axios = require('axios'); @@ -214,7 +215,6 @@ const registerNetworkIpc = (mainWindow) => { cacertFile = cacertArray.find((el) => el); if (cacertFile && cacertFile.length > 1) { try { - const fs = require('fs'); caCrt = fs.readFileSync(cacertFile); httpsAgentRequestFields['ca'] = caCrt; } catch (err) { @@ -223,18 +223,41 @@ const registerNetworkIpc = (mainWindow) => { } } - // proxy configuration const brunoConfig = getBrunoConfig(collectionUid); + const interpolationOptions = { + envVars, + collectionVariables, + processEnvVars + }; + + // client certificate config + const clientCertConfig = get(brunoConfig, 'clientCertificates', []); + + for (clientCert of clientCertConfig) { + const domain = interpolateString(clientCert.domain, interpolationOptions); + const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + if (domain && certFilePath && keyFilePath) { + const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); + + if (request.url.match(hostRegex)) { + try { + httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); + } catch (err) { + console.log('Error reading cert/key file', err); + } + httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions); + break; + } + } + } + + // proxy configuration const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); if (proxyEnabled) { let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions); const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions); const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions); From 3cc2e3d4fa662427f373cfde7740d61ca3c9fb82 Mon Sep 17 00:00:00 2001 From: Easwaran Prem K Date: Sat, 14 Oct 2023 12:29:51 +0530 Subject: [PATCH 02/12] Feature: Support Import postman environment - Fixes issue #193 --- .../EnvironmentList/index.js | 2 + .../ImportEnvironment/index.js | 33 +++++++++ .../Environments/EnvironmentSettings/index.js | 2 + .../ReduxStore/slices/collections/actions.js | 26 +++++++ .../utils/importers/postman-environment.js | 71 +++++++++++++++++++ packages/bruno-electron/src/ipc/collection.js | 22 ++++++ 6 files changed, 156 insertions(+) create mode 100644 packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js create mode 100644 packages/bruno-app/src/utils/importers/postman-environment.js diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index b80cd92a5..e310eb0c1 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -4,6 +4,7 @@ import usePrevious from 'hooks/usePrevious'; import EnvironmentDetails from './EnvironmentDetails'; import CreateEnvironment from '../CreateEnvironment/index'; import StyledWrapper from './StyledWrapper'; +import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentList = ({ collection }) => { const { environments } = collection; @@ -65,6 +66,7 @@ const EnvironmentList = ({ collection }) => {
setOpenCreateModal(true)}> + Create
+ diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js new file mode 100644 index 000000000..1b0fd9dd9 --- /dev/null +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js @@ -0,0 +1,33 @@ +import toast from "react-hot-toast"; +import {toastError} from "utils/common/error"; +import {useDispatch} from "react-redux"; +import {importEnvironment} from "providers/ReduxStore/slices/collections/actions"; +import importPostmanEnvironment from "utils/importers/postman-environment"; +import React from "react"; + +const ImportEnvironment = ({title, collectionUid}) => { + const dispatch = useDispatch(); + + const handleImportPostmanEnvironment = () => { + importPostmanEnvironment() + .then((environment) => { + dispatch(importEnvironment(environment.name, environment.variables, collectionUid)) + .then(() => { + toast.success('Environment imported successfully'); + }) + .catch(() => toast.error('An error occurred while importing the environment')); + }) + .catch((err) => toastError(err, 'Postman Import environment failed')); + }; + + return( + + ); +}; + +export default ImportEnvironment; \ No newline at end of file diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js index 855e0fb31..39c410eed 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import CreateEnvironment from './CreateEnvironment'; import EnvironmentList from './EnvironmentList'; import StyledWrapper from './StyledWrapper'; +import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentSettings = ({ collection, onClose }) => { const { environments } = collection; @@ -28,6 +29,7 @@ const EnvironmentSettings = ({ collection, onClose }) => { > + Create Environment + diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 80c823454..1b36ed65a 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -722,6 +722,32 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) => { }); }; +export const importEnvironment = (name, variables, collectionUid) => (dispatch, getState) => { + return new Promise((resolve, reject) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + if (!collection) { + return reject(new Error('Collection not found')); + } + + ipcRenderer + .invoke('renderer:import-environment', collection.pathname, name, variables) + .then( + dispatch( + updateLastAction({ + collectionUid, + lastAction: { + type: 'ADD_ENVIRONMENT', + payload: name + } + }) + ) + ) + .then(resolve) + .catch(reject); + }); +}; + export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, getState) => { return new Promise((resolve, reject) => { const state = getState(); diff --git a/packages/bruno-app/src/utils/importers/postman-environment.js b/packages/bruno-app/src/utils/importers/postman-environment.js new file mode 100644 index 000000000..61c62311c --- /dev/null +++ b/packages/bruno-app/src/utils/importers/postman-environment.js @@ -0,0 +1,71 @@ +import each from 'lodash/each'; +import fileDialog from 'file-dialog'; +import { BrunoError } from 'utils/common/error'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => resolve(e.target.result); + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const isSecret = (type) => { + return type === 'secret'; +}; + +const importPostmanEnvironmentVariables = (brunoEnvironment, values) => { + brunoEnvironment.variables = brunoEnvironment.variables || []; + + each(values, (i) => { + const brunoEnvironmentVariable = { + name: i.key, + value: i.value, + enabled: i.enabled, + secret: isSecret(i.type) + }; + + brunoEnvironment.variables.push(brunoEnvironmentVariable); + }); +}; + +const importPostmanEnvironment = (environment) => { + const brunoEnvironment = { + name: environment.name, + variables: [] + }; + + importPostmanEnvironmentVariables(brunoEnvironment, environment.values); + return brunoEnvironment; +}; + +const parsePostmanEnvironment = (str) => { + return new Promise((resolve, reject) => { + try { + let environment = JSON.parse(str); + return resolve(importPostmanEnvironment(environment)); + } catch (err) { + console.log(err); + if (err instanceof BrunoError) { + return reject(err); + } + return reject(new BrunoError('Unable to parse the postman environment json file')); + } + }); +}; + +const importEnvironment = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: 'application/json' }) + .then(readFile) + .then(parsePostmanEnvironment) + .then((environment) => resolve(environment)) + .catch((err) => { + console.log(err); + reject(new BrunoError('Import Environment failed')); + }); + }); +}; + +export default importEnvironment; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 944a04f01..b5d4f0541 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -184,6 +184,28 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); + // copy environment + ipcMain.handle('renderer:import-environment', async (event, collectionPathname, name, variables) => { + try { + const envDirPath = path.join(collectionPathname, 'environments'); + if (!fs.existsSync(envDirPath)) { + await createDirectory(envDirPath); + } + + const envFilePath = path.join(envDirPath, `${name}.bru`); + if (fs.existsSync(envFilePath)) { + throw new Error(`environment: ${envFilePath} already exists`); + } + + const content = envJsonToBru({ + variables: variables + }); + await writeFile(envFilePath, content); + } catch (error) { + return Promise.reject(error); + } + }); + // save environment ipcMain.handle('renderer:save-environment', async (event, collectionPathname, environment) => { try { From a2b6bc5970356bcefb6d9f63c3bae36aeb946e07 Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Sat, 14 Oct 2023 15:50:01 +0100 Subject: [PATCH 03/12] feat: add textbox for request timeout --- .../components/Preferences/General/index.js | 43 ++++++++++++++++++- .../src/providers/Preferences/index.js | 6 ++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 637c483e5..4193996c3 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -6,6 +6,7 @@ const General = () => { const { preferences, setPreferences } = usePreferences(); const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification); + const [timeout, setTimeout] = useState(preferences.request.timeout); const handleCheckboxChange = () => { const updatedPreferences = { @@ -25,11 +26,49 @@ const General = () => { }); }; + const handleTimeoutChange = (value) => { + const timeout = value === '' ? 0 : value; + const updatedPreferences = { + ...preferences, + request: { + ...preferences.request, + timeout + } + }; + + setPreferences(updatedPreferences) + .then(() => { + setTimeout(timeout); + }) + .catch((err) => { + console.error(err); + }); + }; + return (
- - SSL Certificate Verification + + +
+
+ + handleTimeoutChange(e.target.value)} + type="text" + className="block textbox w-1/6" + />
); diff --git a/packages/bruno-app/src/providers/Preferences/index.js b/packages/bruno-app/src/providers/Preferences/index.js index 9b0345004..604c40e33 100644 --- a/packages/bruno-app/src/providers/Preferences/index.js +++ b/packages/bruno-app/src/providers/Preferences/index.js @@ -14,13 +14,15 @@ import toast from 'react-hot-toast'; const defaultPreferences = { request: { - sslVerification: true + sslVerification: true, + timeout: 0 } }; const preferencesSchema = Yup.object().shape({ request: Yup.object().shape({ - sslVerification: Yup.boolean() + sslVerification: Yup.boolean(), + timeout: Yup.number() }) }); From 61b705112e0801bbeaad9b091977d1d62d829ebd Mon Sep 17 00:00:00 2001 From: Sai <92389813+oohsai@users.noreply.github.com> Date: Sun, 15 Oct 2023 00:52:33 +0530 Subject: [PATCH 04/12] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..fddeae6e6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +# Description + + +# Contribution Checklist: +- [ ] **The pull request does not introduce any breaking changes** +- [ ] **I have read the [contribution guidelines](https://github.com/usebruno/bruno/blob/main/contributing.md).** +- [ ] **Create an issue and link to the pull request.** From ad3f1b23316ffe6213af538694051276e726775b Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Sun, 15 Oct 2023 18:26:17 +0100 Subject: [PATCH 05/12] feat: use timeout for axios requests --- packages/bruno-electron/src/ipc/network/index.js | 6 ++++++ packages/bruno-js/src/bruno-request.js | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 8e8cb6247..19ca7e43b 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -205,6 +205,8 @@ const registerNetworkIpc = (mainWindow) => { }); const preferences = getPreferences(); + const timeout = get(preferences, 'request.timeout', 0); + request.timeout = timeout; const sslVerification = get(preferences, 'request.sslVerification', true); const httpsAgentRequestFields = {}; if (!sslVerification) { @@ -505,6 +507,8 @@ const registerNetworkIpc = (mainWindow) => { const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request, collectionRoot); const preferences = getPreferences(); + const timeout = get(preferences, 'request.timeout', 0); + request.timeout = timeout; const sslVerification = get(preferences, 'request.sslVerification', true); if (!sslVerification) { @@ -683,6 +687,8 @@ const registerNetworkIpc = (mainWindow) => { }); const preferences = getPreferences(); + const timeout = get(preferences, 'request.timeout', 0); + request.timeout = timeout; const sslVerification = get(preferences, 'request.sslVerification', true); // proxy configuration diff --git a/packages/bruno-js/src/bruno-request.js b/packages/bruno-js/src/bruno-request.js index 099d35111..afbf97873 100644 --- a/packages/bruno-js/src/bruno-request.js +++ b/packages/bruno-js/src/bruno-request.js @@ -5,6 +5,7 @@ class BrunoRequest { this.method = req.method; this.headers = req.headers; this.body = req.data; + this.timeout = req.timeout; } getUrl() { @@ -50,6 +51,14 @@ class BrunoRequest { setMaxRedirects(maxRedirects) { this.req.maxRedirects = maxRedirects; } + + getTimeout() { + return this.req.timeout; + } + + setTimeout(timeout) { + this.req.timeout = timeout; + } } module.exports = BrunoRequest; From 353be75d9cf840cc7fb374e1b3dd1729ff7808ab Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 00:45:54 +0530 Subject: [PATCH 06/12] fix: fixed bug in loading preferences --- packages/bruno-electron/src/store/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 869895cd8..20bc1d4d5 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -29,7 +29,7 @@ class PreferencesStore { getPreferences() { return { - defaultPreferences, + ...defaultPreferences, ...this.store.get('preferences') }; } From 0e1869139ba9bafe1ed0c53a1fcf2009b1f03419 Mon Sep 17 00:00:00 2001 From: Dipin Jagadish Date: Sun, 15 Oct 2023 20:19:08 +0100 Subject: [PATCH 07/12] fix: fixing merge conflicts --- .../components/Preferences/General/index.js | 40 +++++++++---------- .../bruno-electron/src/store/preferences.js | 6 ++- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/General/index.js b/packages/bruno-app/src/components/Preferences/General/index.js index 1c99e5456..b6719b69c 100644 --- a/packages/bruno-app/src/components/Preferences/General/index.js +++ b/packages/bruno-app/src/components/Preferences/General/index.js @@ -15,7 +15,8 @@ const General = ({ close }) => { savePreferences({ ...preferences, request: { - sslVerification + sslVerification, + timeout } }) ).then(() => { @@ -24,37 +25,34 @@ const General = ({ close }) => { }; const handleTimeoutChange = (value) => { - const timeout = value === '' ? 0 : value; - const updatedPreferences = { - ...preferences, - request: { - ...preferences.request, - timeout - } - }; - - setPreferences(updatedPreferences) - .then(() => { - setTimeout(timeout); - }) - .catch((err) => { - console.error(err); - }); + const validTimeout = isNaN(Number(value)) ? timeout : Number(value); + setTimeout(validTimeout); }; return (
+ setSslVerification(!sslVerification)} - className="mr-3 mousetrap" + className="mousetrap h-4 w-4 mr-0" /> -
+
+ + handleTimeoutChange(e.target.value)} + type="text" + className="block textbox w-1/6" + />
diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index 869895cd8..7238ce7cc 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -3,7 +3,8 @@ const Store = require('electron-store'); const defaultPreferences = { request: { - sslVerification: true + sslVerification: true, + timeout: 0 }, font: { codeFont: 'default' @@ -12,7 +13,8 @@ const defaultPreferences = { const preferencesSchema = Yup.object().shape({ request: Yup.object().shape({ - sslVerification: Yup.boolean() + sslVerification: Yup.boolean(), + timeout: Yup.number() }), font: Yup.object().shape({ codeFont: Yup.string().nullable() From 333564f687b2e6f05dff90f5abe3184d850cd091 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 02:07:15 +0530 Subject: [PATCH 08/12] feat(#275): polish client certificate support --- .../ClientCertSettings/StyledWrapper.js | 14 +++++++++- .../ClientCertSettings/index.js | 28 +++++++++++++------ .../components/CollectionSettings/index.js | 19 +++++++++---- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js index bc5ad1565..625bc98e6 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js @@ -2,13 +2,25 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` .settings-label { - width: 80px; + width: 90px; + } + + .certificate-icon { + color: ${(props) => props.theme.colors.text.yellow}; } input { width: 300px; } + .available-certificates { + background-color: ${(props) => props.theme.requestTabPanel.url.bg}; + + button.remove-certificate { + color: ${(props) => props.theme.colors.text.danger}; + } + } + .textbox { border: 1px solid #ccc; padding: 0.15rem 0.45rem; diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js index 280e0f4bb..235e274f5 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js @@ -1,5 +1,7 @@ -import React, { useEffect } from 'react'; +import React from 'react'; +import { IconCertificate, IconTrash, IconWorld } from '@tabler/icons'; import { useFormik } from 'formik'; +import { uuid } from 'utils/common'; import * as Yup from 'yup'; import StyledWrapper from './StyledWrapper'; @@ -29,20 +31,28 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => { return ( -

Current client certificates

-
    +
    + Client Certificates +
    +
      {!clientCertConfig.length ? 'None' : clientCertConfig.map((clientCert) => ( -
    • - Domain: {clientCert.domain} - +
    • +
      +
      + + {clientCert.domain} +
      + +
    • ))}
    -

    New client certicate

    + +

    Add Client Certicate

    setTab('clientCert')}> - Client certificate + Client Certificates
    setTab('docs')}> Docs
-
{getTabPanel(tab)}
+
+ {getTabPanel(tab)} +
); }; From 58fbe2e64b7d7f9f1b9e696f67bd4bb5a7dccf9a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 02:10:18 +0530 Subject: [PATCH 09/12] chore: cleanup --- .../CollectionSettings/Auth/AwsV4Auth/index.js | 1 - .../components/RequestPane/Auth/AwsV4Auth/index.js | 1 - packages/bruno-electron/src/ipc/network/index.js | 11 ----------- 3 files changed, 13 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js index 1fe35eea0..bc9cb67b5 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js @@ -12,7 +12,6 @@ const AwsV4Auth = ({ collection }) => { const { storedTheme } = useTheme(); const awsv4Auth = get(collection, 'root.request.auth.awsv4', {}); - console.log('saved auth', awsv4Auth); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js index 9ed29ac07..7c144fbf8 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js @@ -13,7 +13,6 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => { const { storedTheme } = useTheme(); const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {}); - console.log('saved auth', awsv4Auth); const handleRun = () => dispatch(sendRequest(item, collection.uid)); const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 9d308faf7..405ca7b73 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -210,17 +210,6 @@ const registerNetworkIpc = (mainWindow) => { const httpsAgentRequestFields = {}; if (!sslVerification) { httpsAgentRequestFields['rejectUnauthorized'] = false; - } else { - const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS]; - cacertFile = cacertArray.find((el) => el); - if (cacertFile && cacertFile.length > 1) { - try { - caCrt = fs.readFileSync(cacertFile); - httpsAgentRequestFields['ca'] = caCrt; - } catch (err) { - console.log('Error reading CA cert file:' + cacertFile, err); - } - } } const brunoConfig = getBrunoConfig(collectionUid); From 1f8c4431e06eaccd1b85462995eb8869c790d0dc Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 03:13:06 +0530 Subject: [PATCH 10/12] feat(#589): polish importing postman env --- .../CreateEnvironment/index.js | 4 +- .../EnvironmentList/StyledWrapper.js | 8 ++- .../EnvironmentList/index.js | 17 +++-- .../ImportEnvironment/index.js | 62 ++++++++++--------- .../Environments/EnvironmentSettings/index.js | 18 ++++-- .../GenerateCodeItem/StyledWrapper.js | 1 + .../ReduxStore/slices/collections/actions.js | 28 ++++----- packages/bruno-electron/src/ipc/collection.js | 52 +++------------- 8 files changed, 93 insertions(+), 97 deletions(-) diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js index c0ca7f5ec..e6947bd3a 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CreateEnvironment/index.js @@ -1,6 +1,6 @@ import React, { useEffect, useRef } from 'react'; -import Portal from 'components/Portal/index'; -import Modal from 'components/Modal/index'; +import Portal from 'components/Portal'; +import Modal from 'components/Modal'; import toast from 'react-hot-toast'; import { useFormik } from 'formik'; import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions'; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js index 722a15db1..687cde46c 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js @@ -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%; } .environment-item { @@ -35,7 +36,8 @@ const StyledWrapper = styled.div` } } - .btn-create-environment { + .btn-create-environment, + .btn-import-environment { padding: 8px 10px; cursor: pointer; border-bottom: none; @@ -47,6 +49,10 @@ const StyledWrapper = styled.div` } } } + + .btn-import-environment { + color: ${(props) => props.theme.colors.text.muted}; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index e310eb0c1..44e18455f 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -1,15 +1,19 @@ 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/index'; +import CreateEnvironment from '../CreateEnvironment'; +import { IconUpload } from '@tabler/icons'; +import ImportEnvironment from '../ImportEnvironment'; import StyledWrapper from './StyledWrapper'; -import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; const EnvironmentList = ({ collection }) => { const { environments } = collection; const [selectedEnvironment, setSelectedEnvironment] = useState(null); const [openCreateModal, setOpenCreateModal] = useState(false); + const [openImportModal, setOpenImportModal] = useState(false); const envUids = environments ? environments.map((env) => env.uid) : []; const prevEnvUids = usePrevious(envUids); @@ -49,9 +53,10 @@ const EnvironmentList = ({ collection }) => { return ( {openCreateModal && setOpenCreateModal(false)} />} + {openImportModal && setOpenImportModal(false)} />}
-
+
{environments && environments.length && environments.map((env) => ( @@ -66,7 +71,11 @@ const EnvironmentList = ({ collection }) => {
setOpenCreateModal(true)}> + Create
- + +
setOpenImportModal(true)}> + + Import +
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js index 1b0fd9dd9..5caba79b2 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ImportEnvironment/index.js @@ -1,33 +1,39 @@ -import toast from "react-hot-toast"; -import {toastError} from "utils/common/error"; -import {useDispatch} from "react-redux"; -import {importEnvironment} from "providers/ReduxStore/slices/collections/actions"; -import importPostmanEnvironment from "utils/importers/postman-environment"; -import React from "react"; +import React from 'react'; +import Portal from 'components/Portal'; +import toast from 'react-hot-toast'; +import { useDispatch } from 'react-redux'; +import importPostmanEnvironment from 'utils/importers/postman-environment'; +import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions'; +import { toastError } from 'utils/common/error'; +import Modal from 'components/Modal'; -const ImportEnvironment = ({title, collectionUid}) => { - const dispatch = useDispatch(); +const ImportEnvironment = ({ onClose, collection }) => { + const dispatch = useDispatch(); - const handleImportPostmanEnvironment = () => { - importPostmanEnvironment() - .then((environment) => { - dispatch(importEnvironment(environment.name, environment.variables, collectionUid)) - .then(() => { - toast.success('Environment imported successfully'); - }) - .catch(() => toast.error('An error occurred while importing the environment')); - }) - .catch((err) => toastError(err, 'Postman Import environment failed')); - }; + const handleImportPostmanEnvironment = () => { + importPostmanEnvironment() + .then((environment) => { + dispatch(importEnvironment(environment.name, environment.variables, collection.uid)) + .then(() => { + toast.success('Environment imported successfully'); + onClose(); + }) + .catch(() => toast.error('An error occurred while importing the environment')); + }) + .catch((err) => toastError(err, 'Postman Import environment failed')); + }; - return( - - ); + return ( + + +
+
+ Postman Environment +
+
+
+
+ ); }; -export default ImportEnvironment; \ No newline at end of file +export default ImportEnvironment; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js index 39c410eed..6daccc374 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/index.js @@ -3,11 +3,12 @@ import React, { useState } from 'react'; import CreateEnvironment from './CreateEnvironment'; import EnvironmentList from './EnvironmentList'; import StyledWrapper from './StyledWrapper'; -import ImportEnvironment from "components/Environments/EnvironmentSettings/ImportEnvironment"; +import ImportEnvironment from './ImportEnvironment'; const EnvironmentSettings = ({ collection, onClose }) => { const { environments } = collection; const [openCreateModal, setOpenCreateModal] = useState(false); + const [openImportModal, setOpenImportModal] = useState(false); if (!environments || !environments.length) { return ( @@ -21,15 +22,24 @@ const EnvironmentSettings = ({ collection, onClose }) => { hideCancel={true} > {openCreateModal && setOpenCreateModal(false)} />} -
+ {openImportModal && setOpenImportModal(false)} />} +

No environments found!

+ + Or + + -
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js index f1c1c33e4..635c545e9 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/StyledWrapper.js @@ -9,6 +9,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%; } .generate-code-item { diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 1b36ed65a..38e3c30ed 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -731,20 +731,20 @@ export const importEnvironment = (name, variables, collectionUid) => (dispatch, } ipcRenderer - .invoke('renderer:import-environment', collection.pathname, name, variables) - .then( - dispatch( - updateLastAction({ - collectionUid, - lastAction: { - type: 'ADD_ENVIRONMENT', - payload: name - } - }) - ) + .invoke('renderer:create-environment', collection.pathname, name, variables) + .then( + dispatch( + updateLastAction({ + collectionUid, + lastAction: { + type: 'ADD_ENVIRONMENT', + payload: name + } + }) ) - .then(resolve) - .catch(reject); + ) + .then(resolve) + .catch(reject); }); }; @@ -762,7 +762,7 @@ export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, g } ipcRenderer - .invoke('renderer:copy-environment', collection.pathname, name, baseEnv.variables) + .invoke('renderer:create-environment', collection.pathname, name, baseEnv.variables) .then( dispatch( updateLastAction({ diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index a78294b11..b4acd5eb0 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -140,7 +140,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // create environment - ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name) => { + ipcMain.handle('renderer:create-environment', async (event, collectionPathname, name, variables) => { try { const envDirPath = path.join(collectionPathname, 'environments'); if (!fs.existsSync(envDirPath)) { @@ -152,53 +152,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection throw new Error(`environment: ${envFilePath} already exists`); } - const content = envJsonToBru({ - variables: [] - }); - await writeFile(envFilePath, content); - } catch (error) { - return Promise.reject(error); - } - }); + const environment = { + name: name, + variables: variables || [] + }; - // copy environment - ipcMain.handle('renderer:copy-environment', async (event, collectionPathname, name, baseVariables) => { - try { - const envDirPath = path.join(collectionPathname, 'environments'); - if (!fs.existsSync(envDirPath)) { - await createDirectory(envDirPath); + if (envHasSecrets(environment)) { + environmentSecretsStore.storeEnvSecrets(collectionPathname, environment); } - const envFilePath = path.join(envDirPath, `${name}.bru`); - if (fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} already exists`); - } + const content = envJsonToBru(environment); - const content = envJsonToBru({ - variables: baseVariables - }); - await writeFile(envFilePath, content); - } catch (error) { - return Promise.reject(error); - } - }); - - // copy environment - ipcMain.handle('renderer:import-environment', async (event, collectionPathname, name, variables) => { - try { - const envDirPath = path.join(collectionPathname, 'environments'); - if (!fs.existsSync(envDirPath)) { - await createDirectory(envDirPath); - } - - const envFilePath = path.join(envDirPath, `${name}.bru`); - if (fs.existsSync(envFilePath)) { - throw new Error(`environment: ${envFilePath} already exists`); - } - - const content = envJsonToBru({ - variables: variables - }); await writeFile(envFilePath, content); } catch (error) { return Promise.reject(error); From cdfa839cf33edaf0d7d07c72ca162e6b57f1325a Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 16 Oct 2023 03:41:47 +0530 Subject: [PATCH 11/12] chore: polish request timeout --- .../src/components/Preferences/Font/index.js | 22 +++++++++---------- .../components/Preferences/General/index.js | 20 +++++++++-------- .../bruno-electron/src/ipc/network/index.js | 19 ++++++++-------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/bruno-app/src/components/Preferences/Font/index.js b/packages/bruno-app/src/components/Preferences/Font/index.js index bae23e723..2f27fea8b 100644 --- a/packages/bruno-app/src/components/Preferences/Font/index.js +++ b/packages/bruno-app/src/components/Preferences/Font/index.js @@ -30,18 +30,16 @@ const Font = ({ close }) => { return ( -
- -
+
diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index ffe266f3e..70a74326b 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.24.0", + "version": "v0.25.0", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com",