From 665428a2d078123ce1a8655da8e02b7028dc1ffc Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Thu, 28 Sep 2023 03:06:53 +0530 Subject: [PATCH] feat(#224): proxy support feature - gui layer --- .../ProxySettings/StyledWrapper.js | 27 +++ .../CollectionSettings/ProxySettings/index.js | 190 ++++++++++++++++++ .../CollectionSettings/StyledWrapper.js | 20 ++ .../components/CollectionSettings/index.js | 34 ++++ .../src/components/RequestTabPanel/index.js | 5 + .../RequestTabs/CollectionToolBar/index.js | 15 +- .../RequestTabs/RequestTab/SpecialTab.js | 30 ++- .../RequestTabs/RequestTab/index.js | 4 +- .../providers/App/useCollectionTreeSync.js | 9 +- .../ReduxStore/slices/collections/actions.js | 23 ++- .../ReduxStore/slices/collections/index.js | 9 + .../bruno-electron/src/app/collections.js | 4 +- packages/bruno-electron/src/app/watcher.js | 14 +- packages/bruno-electron/src/ipc/collection.js | 24 ++- .../bruno-schema/src/collections/index.js | 3 +- 15 files changed, 383 insertions(+), 28 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/ProxySettings/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/StyledWrapper.js new file mode 100644 index 000000000..c8f1241c5 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/StyledWrapper.js @@ -0,0 +1,27 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .settings-label { + width: 80px; + } + + .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/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js new file mode 100644 index 000000000..c3746f566 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -0,0 +1,190 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import StyledWrapper from './StyledWrapper'; + +const ProxySettings = ({ proxyConfig, onUpdate }) => { + const formik = useFormik({ + initialValues: { + enabled: proxyConfig.enabled || false, + protocol: proxyConfig.protocol || 'http', + hostname: proxyConfig.hostname || '', + port: proxyConfig.port || '', + auth: { + enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false, + username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', + password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' + } + }, + validationSchema: Yup.object({ + enabled: Yup.boolean(), + protocol: Yup.string().oneOf(['http', 'https']), + hostname: Yup.string().max(1024), + port: Yup.number().min(0).max(65535), + auth: Yup.object({ + enabled: Yup.boolean(), + username: Yup.string().max(1024), + password: Yup.string().max(1024) + }) + }), + onSubmit: (values) => { + onUpdate(values); + } + }); + + useEffect(() => { + formik.setValues({ + enabled: proxyConfig.enabled || false, + protocol: proxyConfig.protocol || 'http', + hostname: proxyConfig.hostname || '', + port: proxyConfig.port || '', + auth: { + enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false, + username: proxyConfig.auth ? proxyConfig.auth.username || '' : '', + password: proxyConfig.auth ? proxyConfig.auth.password || '' : '' + } + }); + }, [proxyConfig]); + + return ( + +

Proxy Settings

+
+
+ + +
+
+ +
+ + +
+
+
+ + + {formik.touched.hostname && formik.errors.hostname ? ( +
{formik.errors.hostname}
+ ) : null} +
+
+ + + {formik.touched.port && formik.errors.port ?
{formik.errors.port}
: null} +
+
+ + +
+
+
+ + + {formik.touched.auth?.username && formik.errors.auth?.username ? ( +
{formik.errors.auth.username}
+ ) : null} +
+
+ + + {formik.touched.auth?.password && formik.errors.auth?.password ? ( +
{formik.errors.auth.password}
+ ) : null} +
+
+
+ +
+
+
+ ); +}; + +export default ProxySettings; diff --git a/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js new file mode 100644 index 000000000..e4d976b0d --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/StyledWrapper.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + table { + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + + li { + background-color: ${(props) => props.theme.bg} !important; + } + } + } + + .muted { + color: ${(props) => props.theme.colors.text.muted}; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js new file mode 100644 index 000000000..98d8aed48 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import get from 'lodash/get'; +import cloneDeep from 'lodash/cloneDeep'; +import toast from 'react-hot-toast'; +import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions'; +import { useDispatch } from 'react-redux'; +import ProxySettings from './ProxySettings'; +import StyledWrapper from './StyledWrapper'; + +const CollectionSettings = ({ collection }) => { + const dispatch = useDispatch(); + + const proxyConfig = get(collection, 'brunoConfig.proxy', {}); + + const onProxySettingsUpdate = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.proxy = 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')); + }; + + return ( + +

Collection Settings

+ + +
+ ); +}; + +export default CollectionSettings; diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index 511fb5cd4..24aefd2ca 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -14,6 +14,7 @@ import QueryUrl from 'components/RequestPane/QueryUrl'; import NetworkError from 'components/ResponsePane/NetworkError'; import RunnerResults from 'components/RunnerResults'; import VariablesEditor from 'components/VariablesEditor'; +import CollectionSettings from 'components/CollectionSettings'; import { DocExplorer } from '@usebruno/graphql-docs'; import StyledWrapper from './StyledWrapper'; @@ -128,6 +129,10 @@ const RequestTabPanel = () => { return ; } + if (focusedTab.type === 'collection-settings') { + return ; + } + const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { return ; diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index c461e3bb4..2103499ce 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { uuid } from 'utils/common'; -import { IconFiles, IconRun, IconEye } from '@tabler/icons'; +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'; @@ -28,6 +28,16 @@ const CollectionToolBar = ({ collection }) => { ); }; + const viewCollectionSettings = () => { + dispatch( + addTab({ + uid: uuid(), + collectionUid: collection.uid, + type: 'collection-settings' + }) + ); + }; + return (
@@ -42,6 +52,9 @@ const CollectionToolBar = ({ collection }) => { + + +
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js index d086d5b90..cb8dfe7e2 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -1,13 +1,31 @@ import React from 'react'; -import { IconVariable } from '@tabler/icons'; +import { IconVariable, IconSettings } from '@tabler/icons'; + +const SpecialTab = ({ handleCloseClick, type }) => { + const getTabInfo = (type) => { + switch (type) { + case 'collection-settings': { + return ( + <> + + Settings + + ); + } + case 'variables': { + return ( + <> + + Variables + + ); + } + } + }; -const SpecialTab = ({ handleCloseClick, text }) => { return ( <> -
- - {text} -
+
{getTabInfo(type)}
handleCloseClick(e)}> { return color; }; - if (tab.type === 'variables') { + if (['collection-settings', 'variables'].includes(tab.type)) { return ( - + ); } diff --git a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js index 858682e82..caf057d5b 100644 --- a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js @@ -11,7 +11,8 @@ import { processEnvUpdateEvent, collectionRenamedEvent, runRequestEvent, - runFolderEvent + runFolderEvent, + brunoConfigUpdateEvent } from 'providers/ReduxStore/slices/collections'; import toast from 'react-hot-toast'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; @@ -27,8 +28,8 @@ const useCollectionTreeSync = () => { const { ipcRenderer } = window; - const _openCollection = (pathname, uid, name) => { - dispatch(openCollectionEvent(uid, pathname, name)); + const _openCollection = (pathname, uid, brunoConfig) => { + dispatch(openCollectionEvent(uid, pathname, brunoConfig)); }; const _collectionTreeUpdated = (type, val) => { @@ -128,6 +129,7 @@ const useCollectionTreeSync = () => { const removeListener10 = ipcRenderer.on('main:console-log', (val) => { console[val.type](...val.args); }); + const removeListener11 = ipcRenderer.on('main:bruno-config-update', (val) => dispatch(brunoConfigUpdateEvent(val))); return () => { removeListener1(); @@ -140,6 +142,7 @@ const useCollectionTreeSync = () => { removeListener8(); removeListener9(); removeListener10(); + removeListener11(); }; }, [isElectron]); }; 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 2ccfdbd4a..d0b3d62fc 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -750,15 +750,32 @@ export const browseDirectory = () => (dispatch, getState) => { }); }; -export const openCollectionEvent = (uid, pathname, name) => (dispatch, getState) => { +export const updateBrunoConfig = (brunoConfig, collectionUid) => (dispatch, getState) => { + const state = getState(); + + const collection = findCollectionByUid(state.collections.collections, collectionUid); + if (!collection) { + return reject(new Error('Collection not found')); + } + + return new Promise((resolve, reject) => { + ipcRenderer + .invoke('renderer:update-bruno-config', brunoConfig, collection.pathname, collectionUid) + .then(resolve) + .catch(reject); + }); +}; + +export const openCollectionEvent = (uid, pathname, brunoConfig) => (dispatch, getState) => { const collection = { version: '1', uid: uid, - name: name, + name: brunoConfig.name, pathname: pathname, items: [], showRunner: false, - collectionVariables: {} + collectionVariables: {}, + brunoConfig: brunoConfig }; return new Promise((resolve, reject) => { diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index e1137aae7..762421822 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -52,6 +52,14 @@ export const collectionsSlice = createSlice({ state.collections.push(collection); } }, + brunoConfigUpdateEvent: (state, action) => { + const { collectionUid, brunoConfig } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + + if (collection) { + collection.brunoConfig = brunoConfig; + } + }, renameCollection: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); @@ -1155,6 +1163,7 @@ export const collectionsSlice = createSlice({ export const { createCollection, + brunoConfigUpdateEvent, renameCollection, removeCollection, updateLastAction, diff --git a/packages/bruno-electron/src/app/collections.js b/packages/bruno-electron/src/app/collections.js index f3c5b574e..01de9c4ba 100644 --- a/packages/bruno-electron/src/app/collections.js +++ b/packages/bruno-electron/src/app/collections.js @@ -59,10 +59,10 @@ const openCollectionDialog = async (win, watcher) => { const openCollection = async (win, watcher, collectionPath, options = {}) => { if (!watcher.hasWatcher(collectionPath)) { try { - const { name } = await getCollectionConfigFile(collectionPath); + const brunoConfig = await getCollectionConfigFile(collectionPath); const uid = generateUidBasedOnHash(collectionPath); - win.webContents.send('main:collection-opened', collectionPath, uid, name); + win.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig); ipcMain.emit('main:collection-opened', win, collectionPath, uid); } catch (err) { if (!options.dontSendDisplayErrors) { diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index c2388d430..c5973e79c 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -178,9 +178,9 @@ const add = async (win, pathname, collectionUid, collectionPath) => { if (isBrunoConfigFile(pathname, collectionPath)) { try { const content = fs.readFileSync(pathname, 'utf8'); - const jsonData = JSON.parse(content); + const brunoConfig = JSON.parse(content); - setBrunoConfig(collectionUid, jsonData); + setBrunoConfig(collectionUid, brunoConfig); } catch (err) { console.error(err); } @@ -303,9 +303,15 @@ const change = async (win, pathname, collectionUid, collectionPath) => { if (isBrunoConfigFile(pathname, collectionPath)) { try { const content = fs.readFileSync(pathname, 'utf8'); - const jsonData = JSON.parse(content); + const brunoConfig = JSON.parse(content); - setBrunoConfig(collectionUid, jsonData); + const payload = { + collectionUid, + brunoConfig: brunoConfig + }; + + setBrunoConfig(collectionUid, brunoConfig); + win.webContents.send('main:bruno-config-update', payload); } catch (err) { console.error(err); } diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 7431d4fed..637a8d479 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -57,14 +57,15 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(dirPath); const uid = generateUidBasedOnHash(dirPath); - const content = await stringifyJson({ + const brunoConfig = { version: '1', name: collectionName, type: 'collection' - }); + }; + const content = await stringifyJson(brunoConfig); await writeFile(path.join(dirPath, 'bruno.json'), content); - mainWindow.webContents.send('main:collection-opened', dirPath, uid, collectionName); + mainWindow.webContents.send('main:collection-opened', dirPath, uid, brunoConfig); ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid); return; @@ -356,14 +357,15 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection await createDirectory(collectionPath); const uid = generateUidBasedOnHash(collectionPath); - const content = await stringifyJson({ + const brunoConfig = { version: '1', name: collection.name, type: 'collection' - }); + }; + const content = await stringifyJson(brunoConfig); await writeFile(path.join(collectionPath, 'bruno.json'), content); - mainWindow.webContents.send('main:collection-opened', collectionPath, uid, collectionName); + mainWindow.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig); ipcMain.emit('main:collection-opened', mainWindow, collectionPath, uid); lastOpenedCollections.add(collectionPath); @@ -451,6 +453,16 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:set-preferences', async (event, preferences) => { setPreferences(preferences); }); + + ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionUid) => { + try { + const brunoConfigPath = path.join(collectionPath, 'bruno.json'); + const content = await stringifyJson(brunoConfig); + await writeFile(brunoConfigPath, content); + } catch (error) { + return Promise.reject(error); + } + }); }; const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index ba2256a53..6d6240639 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -128,7 +128,8 @@ const collectionSchema = Yup.object({ runnerResult: Yup.object({ items: Yup.array() }), - collectionVariables: Yup.object() + collectionVariables: Yup.object(), + brunoConfig: Yup.object() }) .noUnknown(true) .strict();