mirror of
https://github.com/usebruno/bruno.git
synced 2025-02-16 09:50:17 +01:00
Merge branch 'main' into feature/filter-by-url
This commit is contained in:
commit
9e565273da
@ -40,10 +40,15 @@ const Wrapper = styled.div`
|
||||
color: ${(props) => props.theme.dropdown.iconColor};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover:not(:disabled) {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
&.border-top {
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ const Script = ({ collection, folder }) => {
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
Pre and post-request scripts that will run before and after any request inside this folder is sent.
|
||||
</div>
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
@ -56,8 +56,8 @@ const Script = ({ collection, folder }) => {
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
|
@ -50,7 +50,7 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<StyledWrapper className="flex flex-col h-full">
|
||||
<div className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
|
@ -74,7 +74,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
||||
<div
|
||||
className="tooltip mr-3"
|
||||
className="tooltip mx-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!item.draft) return;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, Fragment } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
@ -12,12 +12,18 @@ import ConfirmRequestClose from './ConfirmRequestClose';
|
||||
import RequestTabNotFound from './RequestTabNotFound';
|
||||
import SpecialTab from './SpecialTab';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import CloneCollectionItem from 'components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index';
|
||||
import NewRequest from 'components/Sidebar/NewRequest/index';
|
||||
|
||||
const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
||||
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const handleCloseClick = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
@ -28,6 +34,19 @@ const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleRightClick = (_event) => {
|
||||
const menuDropdown = dropdownTippyRef.current;
|
||||
if (!menuDropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (menuDropdown.state.isShown) {
|
||||
menuDropdown.hide();
|
||||
} else {
|
||||
menuDropdown.show();
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = (e) => {
|
||||
if (e.button === 1) {
|
||||
e.stopPropagation();
|
||||
@ -143,6 +162,7 @@ const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
)}
|
||||
<div
|
||||
className="flex items-baseline tab-label pl-2"
|
||||
onContextMenu={handleRightClick}
|
||||
onMouseUp={(e) => {
|
||||
if (!item.draft) return handleMouseUp(e);
|
||||
|
||||
@ -159,6 +179,15 @@ const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
<span className="ml-1 tab-name" title={item.name}>
|
||||
{item.name}
|
||||
</span>
|
||||
<RequestTabMenu
|
||||
onDropdownCreate={onDropdownCreate}
|
||||
tabIndex={tabIndex}
|
||||
collectionRequestTabs={collectionRequestTabs}
|
||||
tabItem={item}
|
||||
collection={collection}
|
||||
dropdownTippyRef={dropdownTippyRef}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex px-2 close-icon-container"
|
||||
@ -195,4 +224,124 @@ const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
);
|
||||
};
|
||||
|
||||
function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, collection, dropdownTippyRef, dispatch }) {
|
||||
const [showCloneRequestModal, setShowCloneRequestModal] = useState(false);
|
||||
const [showAddNewRequestModal, setShowAddNewRequestModal] = useState(false);
|
||||
|
||||
const totalTabs = collectionRequestTabs.length || 0;
|
||||
const currentTabUid = collectionRequestTabs[tabIndex]?.uid;
|
||||
const currentTabItem = findItemInCollection(collection, currentTabUid);
|
||||
|
||||
const hasLeftTabs = tabIndex !== 0;
|
||||
const hasRightTabs = totalTabs > tabIndex + 1;
|
||||
const hasOtherTabs = totalTabs > 1;
|
||||
|
||||
async function handleCloseTab(event, tabUid) {
|
||||
event.stopPropagation();
|
||||
dropdownTippyRef.current.hide();
|
||||
|
||||
if (!tabUid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const item = findItemInCollection(collection, tabUid);
|
||||
// silently save unsaved changes before closing the tab
|
||||
if (item.draft) {
|
||||
await dispatch(saveRequest(item.uid, collection.uid, true));
|
||||
}
|
||||
|
||||
dispatch(closeTabs({ tabUids: [tabUid] }));
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
function handleCloseOtherTabs(event) {
|
||||
dropdownTippyRef.current.hide();
|
||||
|
||||
const otherTabs = collectionRequestTabs.filter((_, index) => index !== tabIndex);
|
||||
otherTabs.forEach((tab) => handleCloseTab(event, tab.uid));
|
||||
}
|
||||
|
||||
function handleCloseTabsToTheLeft(event) {
|
||||
dropdownTippyRef.current.hide();
|
||||
|
||||
const leftTabs = collectionRequestTabs.filter((_, index) => index < tabIndex);
|
||||
leftTabs.forEach((tab) => handleCloseTab(event, tab.uid));
|
||||
}
|
||||
|
||||
function handleCloseTabsToTheRight(event) {
|
||||
dropdownTippyRef.current.hide();
|
||||
|
||||
const rightTabs = collectionRequestTabs.filter((_, index) => index > tabIndex);
|
||||
rightTabs.forEach((tab) => handleCloseTab(event, tab.uid));
|
||||
}
|
||||
|
||||
function handleCloseSavedTabs(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const savedTabs = collection.items.filter((item) => !item.draft);
|
||||
const savedTabIds = savedTabs.map((item) => item.uid) || [];
|
||||
dispatch(closeTabs({ tabUids: savedTabIds }));
|
||||
}
|
||||
|
||||
function handleCloseAllTabs(event) {
|
||||
collectionRequestTabs.forEach((tab) => handleCloseTab(event, tab.uid));
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{showAddNewRequestModal && (
|
||||
<NewRequest collection={collection} onClose={() => setShowAddNewRequestModal(false)} />
|
||||
)}
|
||||
|
||||
{showCloneRequestModal && (
|
||||
<CloneCollectionItem
|
||||
item={currentTabItem}
|
||||
collection={collection}
|
||||
onClose={() => setShowCloneRequestModal(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<span></span>} placement="bottom-start">
|
||||
<button
|
||||
className="dropdown-item w-full"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setShowAddNewRequestModal(true);
|
||||
}}
|
||||
>
|
||||
New Request
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item w-full"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setShowCloneRequestModal(true);
|
||||
}}
|
||||
>
|
||||
Clone Request
|
||||
</button>
|
||||
<button className="dropdown-item w-full" onClick={(e) => handleCloseTab(e, currentTabUid)}>
|
||||
Close
|
||||
</button>
|
||||
<button disabled={!hasOtherTabs} className="dropdown-item w-full" onClick={handleCloseOtherTabs}>
|
||||
Close Others
|
||||
</button>
|
||||
<button disabled={!hasLeftTabs} className="dropdown-item w-full" onClick={handleCloseTabsToTheLeft}>
|
||||
Close to the Left
|
||||
</button>
|
||||
<button disabled={!hasRightTabs} className="dropdown-item w-full" onClick={handleCloseTabsToTheRight}>
|
||||
Close to the Right
|
||||
</button>
|
||||
<button className="dropdown-item w-full" onClick={handleCloseSavedTabs}>
|
||||
Close Saved
|
||||
</button>
|
||||
<button className="dropdown-item w-full" onClick={handleCloseAllTabs}>
|
||||
Close All
|
||||
</button>
|
||||
</Dropdown>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default RequestTab;
|
||||
|
@ -7,7 +7,6 @@ const Wrapper = styled.div`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
@ -110,7 +110,14 @@ const RequestTabs = () => {
|
||||
role="tab"
|
||||
onClick={() => handleClick(tab)}
|
||||
>
|
||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} folderUid={tab.folderUid} />
|
||||
<RequestTab
|
||||
collectionRequestTabs={collectionRequestTabs}
|
||||
tabIndex={index}
|
||||
key={tab.uid}
|
||||
tab={tab}
|
||||
collection={activeCollection}
|
||||
folderUid={tab.folderUid}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
|
@ -109,7 +109,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item ? item.uid : null,
|
||||
headers: request.headers,
|
||||
body: request.body
|
||||
body: request.body,
|
||||
auth: request.auth
|
||||
})
|
||||
)
|
||||
.then(() => onClose())
|
||||
|
@ -129,7 +129,7 @@ const Sidebar = () => {
|
||||
Star
|
||||
</GitHubButton> */}
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.24.0</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.25.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -174,7 +174,7 @@ class SingleLineEditor extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-row justify-between w-full overflow-x-auto">
|
||||
<StyledWrapper ref={this.editorRef} className="single-line-editor grow" />
|
||||
{this.secretEye(this.props.isSecret)}
|
||||
</div>
|
||||
|
@ -9,9 +9,11 @@ import CreateCollection from 'components/Sidebar/CreateCollection';
|
||||
import ImportCollection from 'components/Sidebar/ImportCollection';
|
||||
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useDictionary } from 'providers/Dictionary/index';
|
||||
|
||||
const Welcome = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { dictionary } = useDictionary();
|
||||
const [importedCollection, setImportedCollection] = useState(null);
|
||||
const [importedTranslationLog, setImportedTranslationLog] = useState({});
|
||||
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||
@ -20,7 +22,7 @@ const Welcome = () => {
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
|
||||
(err) => console.log(err) && toast.error(dictionary.errorWhileOpeningCollection)
|
||||
);
|
||||
};
|
||||
|
||||
@ -38,12 +40,12 @@ const Welcome = () => {
|
||||
.then(() => {
|
||||
setImportCollectionLocationModalOpen(false);
|
||||
setImportedCollection(null);
|
||||
toast.success('Collection imported successfully');
|
||||
toast.success(dictionary.collectionImportedSuccessfully);
|
||||
})
|
||||
.catch((err) => {
|
||||
setImportCollectionLocationModalOpen(false);
|
||||
console.error(err);
|
||||
toast.error('An error occurred while importing the collection. Check the logs for more information.');
|
||||
toast.error(dictionary.errorWhileImportingCollection);
|
||||
});
|
||||
};
|
||||
|
||||
@ -66,46 +68,45 @@ const Welcome = () => {
|
||||
<Bruno width={50} />
|
||||
</div>
|
||||
<div className="text-xl font-semibold select-none">bruno</div>
|
||||
<div className="mt-4">Opensource IDE for exploring and testing APIs</div>
|
||||
<div className="mt-4">{dictionary.aboutBruno}</div>
|
||||
|
||||
<div className="uppercase font-semibold heading mt-10">Collections</div>
|
||||
<div className="uppercase font-semibold heading mt-10">{dictionary.collections}</div>
|
||||
<div className="mt-4 flex items-center collection-options select-none">
|
||||
<div className="flex items-center" onClick={() => setCreateCollectionModalOpen(true)}>
|
||||
<IconPlus size={18} strokeWidth={2} />
|
||||
<span className="label ml-2" id="create-collection">
|
||||
Create Collection
|
||||
{dictionary.createCollection}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center ml-6" onClick={handleOpenCollection}>
|
||||
<IconFolders size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Open Collection</span>
|
||||
<span className="label ml-2">{dictionary.openCollection}</span>
|
||||
</div>
|
||||
<div className="flex items-center ml-6" onClick={() => setImportCollectionModalOpen(true)}>
|
||||
<IconDownload size={18} strokeWidth={2} />
|
||||
<span className="label ml-2" id="import-collection">
|
||||
Import Collection
|
||||
{dictionary.importCollection}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="uppercase font-semibold heading mt-10 pt-6">Links</div>
|
||||
<div className="mt-4 flex flex-col collection-options select-none">
|
||||
<div className="flex items-center mt-2">
|
||||
<a href="https://docs.usebruno.com" target="_blank" className="inline-flex items-center">
|
||||
<IconBook size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Documentation</span>
|
||||
<span className="label ml-2">{dictionary.documentation}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="inline-flex items-center">
|
||||
<IconSpeakerphone size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Report Issues</span>
|
||||
<span className="label ml-2">{dictionary.reportIssues}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">GitHub</span>
|
||||
<span className="label ml-2">{dictionary.gitHub}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
14
packages/bruno-app/src/dictionaries/en.js
Normal file
14
packages/bruno-app/src/dictionaries/en.js
Normal file
@ -0,0 +1,14 @@
|
||||
export default {
|
||||
aboutBruno: 'Opensource IDE for exploring and testing APIs',
|
||||
collections: 'Collections',
|
||||
createCollection: 'Create Collection',
|
||||
openCollection: 'Open Collection',
|
||||
importCollection: 'Import Collection',
|
||||
documentation: 'Documentation',
|
||||
reportIssues: 'Report Issues',
|
||||
gitHub: 'GitHub',
|
||||
collectionImportedSuccessfully: 'Collection imported successfully',
|
||||
errorWhileOpeningCollection: 'An error occurred while opening the collection',
|
||||
errorWhileImportingCollection:
|
||||
'An error occurred while importing the collection. Check the logs for more information.'
|
||||
};
|
5
packages/bruno-app/src/dictionaries/index.js
Normal file
5
packages/bruno-app/src/dictionaries/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import en from './en.js';
|
||||
|
||||
export const dictionaries = {
|
||||
en
|
||||
};
|
@ -14,6 +14,7 @@ import 'codemirror/lib/codemirror.css';
|
||||
import 'graphiql/graphiql.min.css';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
import '@usebruno/graphql-docs/dist/esm/index.css';
|
||||
import { DictionaryProvider } from 'providers/Dictionary/index';
|
||||
|
||||
function SafeHydrate({ children }) {
|
||||
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;
|
||||
@ -59,13 +60,15 @@ function MyApp({ Component, pageProps }) {
|
||||
<NoSsr>
|
||||
<Provider store={ReduxStore}>
|
||||
<ThemeProvider>
|
||||
<ToastProvider>
|
||||
<AppProvider>
|
||||
<HotkeysProvider>
|
||||
<Component {...pageProps} />
|
||||
</HotkeysProvider>
|
||||
</AppProvider>
|
||||
</ToastProvider>
|
||||
<DictionaryProvider>
|
||||
<ToastProvider>
|
||||
<AppProvider>
|
||||
<HotkeysProvider>
|
||||
<Component {...pageProps} />
|
||||
</HotkeysProvider>
|
||||
</AppProvider>
|
||||
</ToastProvider>
|
||||
</DictionaryProvider>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</NoSsr>
|
||||
|
@ -60,7 +60,7 @@ const trackStart = () => {
|
||||
event: 'start',
|
||||
properties: {
|
||||
os: platformLib.os.family,
|
||||
version: '1.24.0'
|
||||
version: '1.25.0'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
28
packages/bruno-app/src/providers/Dictionary/index.js
Normal file
28
packages/bruno-app/src/providers/Dictionary/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { useState, useContext } from 'react';
|
||||
import { dictionaries } from 'src/dictionaries/index';
|
||||
|
||||
export const DictionaryContext = React.createContext();
|
||||
|
||||
const DictionaryProvider = (props) => {
|
||||
const [language, setLanguage] = useState('en');
|
||||
const dictionary = dictionaries[language] ?? dictionaries.en;
|
||||
|
||||
return (
|
||||
<DictionaryContext.Provider {...props} value={{ language, setLanguage, dictionary }}>
|
||||
<>{props.children}</>
|
||||
</DictionaryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useDictionary = () => {
|
||||
const context = useContext(DictionaryContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error(`useDictionary must be used within a DictionaryProvider`);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export { useDictionary, DictionaryProvider };
|
@ -154,6 +154,31 @@ export const HotkeysProvider = (props) => {
|
||||
};
|
||||
}, [activeTabUid]);
|
||||
|
||||
// close all tabs
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+shift+w', 'ctrl+shift+w'], (e) => {
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (activeTab) {
|
||||
const collection = findCollectionByUid(collections, activeTab.collectionUid);
|
||||
|
||||
if (collection) {
|
||||
const tabUids = tabs.filter((tab) => tab.collectionUid === collection.uid).map((tab) => tab.uid);
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: tabUids
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // this stops the event bubbling
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind(['command+shift+w', 'ctrl+shift+w']);
|
||||
};
|
||||
}, [activeTabUid, tabs, collections, dispatch]);
|
||||
|
||||
return (
|
||||
<HotkeysContext.Provider {...props} value="hotkey">
|
||||
{showSaveRequestModal && (
|
||||
|
@ -698,7 +698,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
|
||||
};
|
||||
|
||||
export const newHttpRequest = (params) => (dispatch, getState) => {
|
||||
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body } = params;
|
||||
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
@ -730,6 +730,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
||||
sparql: null,
|
||||
multipartForm: null,
|
||||
formUrlEncoded: null
|
||||
},
|
||||
auth: auth ?? {
|
||||
mode: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -4,8 +4,8 @@ import find from 'lodash/find';
|
||||
|
||||
export const doesRequestMatchSearchText = (request, searchText = '') => {
|
||||
return (
|
||||
request.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
request.request.url.toLowerCase().includes(searchText.toLowerCase())
|
||||
request?.name?.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
request?.request?.url?.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -123,7 +123,7 @@ const curlToJson = (curlCommand) => {
|
||||
request.urlWithoutQuery = 'http://' + request.urlWithoutQuery;
|
||||
}
|
||||
|
||||
requestJson.url = request.urlWithoutQuery.replace(/\/$/, '');
|
||||
requestJson.url = request.urlWithoutQuery;
|
||||
requestJson.raw_url = request.url;
|
||||
requestJson.method = request.method;
|
||||
|
||||
@ -160,14 +160,15 @@ const curlToJson = (curlCommand) => {
|
||||
}
|
||||
|
||||
if (request.auth) {
|
||||
const splitAuth = request.auth.split(':');
|
||||
const user = splitAuth[0] || '';
|
||||
const password = splitAuth[1] || '';
|
||||
|
||||
requestJson.auth = {
|
||||
user: repr(user),
|
||||
password: repr(password)
|
||||
};
|
||||
if(request.auth.mode === 'basic'){
|
||||
requestJson.auth = {
|
||||
mode: 'basic',
|
||||
basic: {
|
||||
username: repr(request.auth.basic?.username),
|
||||
password: repr(request.auth.basic?.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(requestJson).length ? requestJson : {};
|
||||
|
@ -75,4 +75,15 @@ describe('curlToJson', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return and parse a simple curl command with a trailing slash', () => {
|
||||
const curlCommand = 'curl https://www.usebruno.com/';
|
||||
const result = curlToJson(curlCommand);
|
||||
|
||||
expect(result).toEqual({
|
||||
url: 'https://www.usebruno.com/',
|
||||
raw_url: 'https://www.usebruno.com/',
|
||||
method: 'get'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -56,7 +56,8 @@ export const getRequestFromCurlCommand = (curlCommand) => {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
body,
|
||||
headers: headers
|
||||
headers: headers,
|
||||
auth: request.auth
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -36,7 +36,8 @@ const parseCurlCommand = (curlCommand) => {
|
||||
boolean: ['I', 'head', 'compressed', 'L', 'k', 'silent', 's', 'G', 'get'],
|
||||
alias: {
|
||||
H: 'header',
|
||||
A: 'user-agent'
|
||||
A: 'user-agent',
|
||||
u: 'user'
|
||||
}
|
||||
});
|
||||
|
||||
@ -187,10 +188,21 @@ const parseCurlCommand = (curlCommand) => {
|
||||
}
|
||||
|
||||
urlObject.search = null; // Clean out the search/query portion.
|
||||
|
||||
let urlWithoutQuery = URL.format(urlObject);
|
||||
let urlHost = urlObject?.host;
|
||||
if (!url?.includes(`${urlHost}/`)) {
|
||||
if (urlWithoutQuery && urlHost) {
|
||||
const [beforeHost, afterHost] = urlWithoutQuery.split(urlHost);
|
||||
urlWithoutQuery = beforeHost + urlHost + afterHost?.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
const request = {
|
||||
url: url,
|
||||
urlWithoutQuery: URL.format(urlObject)
|
||||
url,
|
||||
urlWithoutQuery
|
||||
};
|
||||
|
||||
if (compressed) {
|
||||
request.compressed = true;
|
||||
}
|
||||
@ -226,12 +238,19 @@ const parseCurlCommand = (curlCommand) => {
|
||||
request.data = parsedArguments['data-urlencode'];
|
||||
}
|
||||
|
||||
if (parsedArguments.u) {
|
||||
request.auth = parsedArguments.u;
|
||||
}
|
||||
if (parsedArguments.user) {
|
||||
request.auth = parsedArguments.user;
|
||||
if (parsedArguments.user && typeof parsedArguments.user === 'string') {
|
||||
const basicAuth = parsedArguments.user.split(':')
|
||||
const username = basicAuth[0] || ''
|
||||
const password = basicAuth[1] || ''
|
||||
request.auth = {
|
||||
mode: 'basic',
|
||||
basic: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(request.data)) {
|
||||
request.dataArray = request.data;
|
||||
request.data = request.data.join('&');
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "v1.24.0",
|
||||
"version": "v1.25.0",
|
||||
"name": "bruno",
|
||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||
"homepage": "https://www.usebruno.com",
|
||||
|
@ -464,9 +464,10 @@ class Watcher {
|
||||
.on('unlink', (pathname) => unlink(win, pathname, collectionUid, watchPath))
|
||||
.on('unlinkDir', (pathname) => unlinkDir(win, pathname, collectionUid, watchPath))
|
||||
.on('error', (error) => {
|
||||
// `EMFILE` is an error code thrown when to many files are watched at the same time see: https://github.com/usebruno/bruno/issues/627
|
||||
// `ENOSPC` stands for "Error No space" but is also thrown if the file watcher limit is reached.
|
||||
// To prevent loops `!forcePolling` is checked.
|
||||
if (error.code === 'ENOSPC' && !startedNewWatcher && !forcePolling) {
|
||||
if ((error.code === 'ENOSPC' || error.code === 'EMFILE') && !startedNewWatcher && !forcePolling) {
|
||||
// This callback is called for every file the watcher is trying to watch. To prevent a spam of messages and
|
||||
// Multiple watcher being started `startedNewWatcher` is set to prevent this.
|
||||
startedNewWatcher = true;
|
||||
|
@ -1165,12 +1165,22 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
return `response.${extension}`;
|
||||
};
|
||||
|
||||
const fileName =
|
||||
getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader();
|
||||
const getEncodingFormat = () => {
|
||||
const contentType = getHeaderValue('content-type');
|
||||
const extension = mime.extension(contentType) || 'txt';
|
||||
return ['json', 'xml', 'html', 'yml', 'yaml', 'txt'].includes(extension) ? 'utf-8' : 'base64';
|
||||
};
|
||||
|
||||
const determineFileName = () => {
|
||||
return (
|
||||
getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader()
|
||||
);
|
||||
};
|
||||
|
||||
const fileName = determineFileName();
|
||||
const filePath = await chooseFileToSave(mainWindow, fileName);
|
||||
if (filePath) {
|
||||
await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, 'base64'));
|
||||
await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, getEncodingFormat()));
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
|
Loading…
Reference in New Issue
Block a user