mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 18:08:32 +00:00
Compare commits
7 Commits
feat/proxy
...
fix/json-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e98451d4 | ||
|
|
df2e18bedd | ||
|
|
9a88db7e56 | ||
|
|
4a4439f48e | ||
|
|
4710928407 | ||
|
|
33804f4c7b | ||
|
|
e0858d1c99 |
@@ -39,7 +39,7 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
|
||||
|
||||
[Download Bruno](https://www.usebruno.com/downloads)
|
||||
|
||||
📢 Sieh Dir unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
|
||||
📢 Sehen Sie sich unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
|
||||
|
||||
 <br /><br />
|
||||
|
||||
@@ -48,13 +48,13 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
|
||||
Die meisten unserer Funktionen sind kostenlos und quelloffen.
|
||||
Wir bemühen uns um ein Gleichgewicht zwischen [Open-Source-Prinzipien und Nachhaltigkeit](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
|
||||
Sie können die [Golden Edition](https://www.usebruno.com/pricing) vorbestellen ~~$19~~ **$9** ! <br/>
|
||||
|
||||
### Installation
|
||||
|
||||
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.
|
||||
|
||||
Du kannst Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
Sie können Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
|
||||
```sh
|
||||
# Auf Mac via Homebrew
|
||||
@@ -123,11 +123,11 @@ Oder einer Versionskontrolle deiner Wahl
|
||||
|
||||
### Unterstützung ❤️
|
||||
|
||||
Wuff! Wenn du dieses Projekt magst, klick auf den ⭐ Button !!
|
||||
Wuff! Wenn du dieses Projekt magst, klick den ⭐ Button !!
|
||||
|
||||
### Teile Erfahrungsberichte 📣
|
||||
|
||||
Wenn Bruno dir und in deinem Team bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte in unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
|
||||
Wenn Bruno dir und in deinen Teams bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
|
||||
|
||||
### Bereitstellung in neuen Paket-Managern
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ const StyledWrapper = styled.div`
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
font-family: ${(props) => (props.font ? props.font : 'default')};
|
||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||
line-break: anywhere;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ if (!SERVER_RENDERED) {
|
||||
'bru.setVar(key,value)',
|
||||
'bru.deleteVar(key)',
|
||||
'bru.setNextRequest(requestName)',
|
||||
'req.disableParsingResponseJson()',
|
||||
'req.disableParsingResponseJson()'
|
||||
'bru.getRequestVar(key)',
|
||||
'bru.sleep(ms)'
|
||||
];
|
||||
@@ -333,7 +333,6 @@ export default class CodeEditor extends React.Component {
|
||||
className="h-full w-full flex flex-col relative"
|
||||
aria-label="Code Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
|
||||
@@ -10,9 +10,8 @@ import StyledWrapper from './StyledWrapper';
|
||||
import { useRef } from 'react';
|
||||
import path from 'path';
|
||||
import slash from 'utils/common/slash';
|
||||
import { isWindowsOS } from 'utils/common/platform';
|
||||
|
||||
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
const certFilePathInputRef = useRef();
|
||||
const keyFilePathInputRef = useRef();
|
||||
const pfxFilePathInputRef = useRef();
|
||||
@@ -68,15 +67,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
});
|
||||
|
||||
const getFile = (e) => {
|
||||
if (e.files?.[0]?.path) {
|
||||
let relativePath;
|
||||
if (isWindowsOS()) {
|
||||
relativePath = slash(path.win32.relative(root, e.files[0].path));
|
||||
} else {
|
||||
relativePath = path.posix.relative(root, e.files[0].path);
|
||||
}
|
||||
formik.setFieldValue(e.name, relativePath);
|
||||
}
|
||||
e.files?.[0]?.path && formik.setFieldValue(e.name, e.files?.[0]?.path);
|
||||
};
|
||||
|
||||
const resetFileInputFields = () => {
|
||||
@@ -111,14 +102,10 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
: clientCertConfig.map((clientCert) => (
|
||||
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div className="flex w-full items-center">
|
||||
<div className="flex items-center">
|
||||
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
|
||||
{clientCert.domain}
|
||||
</div>
|
||||
<div className="flex w-full items-center">
|
||||
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
|
||||
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
|
||||
</div>
|
||||
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
|
||||
<IconTrash size={18} strokeWidth={1.5} />
|
||||
</button>
|
||||
|
||||
@@ -46,7 +46,6 @@ const Docs = ({ collection }) => {
|
||||
onSave={onSave}
|
||||
mode="application/text"
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
) : (
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import * as Yup from 'yup';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -104,7 +104,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label flex items-center" htmlFor="enabled">
|
||||
Config
|
||||
<InfoTip
|
||||
<Tooltip
|
||||
text={`
|
||||
<div>
|
||||
<ul>
|
||||
@@ -114,7 +114,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
</ul>
|
||||
</div>
|
||||
`}
|
||||
infotipId="request-var"
|
||||
tooltipId="request-var"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
@@ -336,4 +336,4 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ProxySettings;
|
||||
export default ProxySettings;
|
||||
|
||||
@@ -52,7 +52,6 @@ const Script = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
@@ -65,7 +64,6 @@ const Script = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ const Tests = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -95,7 +95,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'clientCert': {
|
||||
return (
|
||||
<ClientCertSettings
|
||||
root={collection.pathname}
|
||||
clientCertConfig={clientCertConfig}
|
||||
onUpdate={onClientCertSettingsUpdate}
|
||||
onRemove={onClientCertSettingsRemove}
|
||||
|
||||
@@ -47,7 +47,6 @@ const Documentation = ({ item, collection }) => {
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
value={docs || ''}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
|
||||
@@ -54,7 +54,6 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
@@ -67,7 +66,6 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ const Tests = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
@@ -82,14 +82,14 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
} from 'providers/ReduxStore/slices/notifications';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { humanizeDate, relativeDate } from 'utils/common';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
|
||||
@@ -22,7 +20,6 @@ const Notifications = () => {
|
||||
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
|
||||
const [selectedNotification, setSelectedNotification] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const notificationsStartIndex = (pageNumber - 1) * PAGE_SIZE;
|
||||
const notificationsEndIndex = pageNumber * PAGE_SIZE;
|
||||
@@ -88,22 +85,21 @@ const Notifications = () => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<a
|
||||
title="Notifications"
|
||||
className="relative cursor-pointer"
|
||||
onClick={() => {
|
||||
dispatch(fetchNotifications());
|
||||
setShowNotificationsModal(true);
|
||||
}}
|
||||
>
|
||||
<ToolHint text="Notifications" toolhintId="Notifications" offset={8} >
|
||||
<IconBell
|
||||
size={18}
|
||||
strokeWidth={1.5}
|
||||
className={`mr-2 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
/>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<span className="notification-count text-xs">{unreadNotifications.length}</span>
|
||||
)}
|
||||
</ToolHint>
|
||||
<IconBell
|
||||
size={18}
|
||||
strokeWidth={1.5}
|
||||
className={`mr-2 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
/>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<span className="notification-count text-xs">{unreadNotifications.length}</span>
|
||||
)}
|
||||
</a>
|
||||
|
||||
{showNotificationsModal && (
|
||||
@@ -133,8 +129,9 @@ const Notifications = () => {
|
||||
{notifications?.slice(notificationsStartIndex, notificationsEndIndex)?.map((notification) => (
|
||||
<li
|
||||
key={notification.id}
|
||||
className={`p-4 flex flex-col justify-center ${selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
|
||||
}`}
|
||||
className={`p-4 flex flex-col justify-center ${
|
||||
selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
|
||||
}`}
|
||||
onClick={handleNotificationItemClick(notification)}
|
||||
>
|
||||
<div className="notification-title w-full">{notification?.title}</div>
|
||||
@@ -144,8 +141,9 @@ const Notifications = () => {
|
||||
</ul>
|
||||
<div className="w-full pagination flex flex-row gap-4 justify-center p-2 items-center text-xs">
|
||||
<button
|
||||
className={`pl-2 pr-2 py-3 select-none ${pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
|
||||
}`}
|
||||
className={`pl-2 pr-2 py-3 select-none ${
|
||||
pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
|
||||
}`}
|
||||
onClick={handlePrev}
|
||||
>
|
||||
{'Prev'}
|
||||
@@ -161,8 +159,9 @@ const Notifications = () => {
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={`pl-2 pr-2 py-3 select-none ${pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
|
||||
}`}
|
||||
className={`pl-2 pr-2 py-3 select-none ${
|
||||
pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
|
||||
}`}
|
||||
onClick={handleNext}
|
||||
>
|
||||
{'Next'}
|
||||
|
||||
@@ -9,25 +9,17 @@ const Font = ({ close }) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
|
||||
const [codeFontSize, setCodeFontSize] = useState(get(preferences, 'font.codeFontSize', '14'));
|
||||
|
||||
const handleCodeFontChange = (event) => {
|
||||
const handleInputChange = (event) => {
|
||||
setCodeFont(event.target.value);
|
||||
};
|
||||
|
||||
const handleCodeFontSizeChange = (event) => {
|
||||
// Restrict to min/max value
|
||||
const clampedSize = Math.max(1, Math.min(event.target.value, 32));
|
||||
setCodeFontSize(clampedSize);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(
|
||||
savePreferences({
|
||||
...preferences,
|
||||
font: {
|
||||
codeFont,
|
||||
codeFontSize
|
||||
codeFont
|
||||
}
|
||||
})
|
||||
).then(() => {
|
||||
@@ -37,33 +29,17 @@ const Font = ({ close }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<div className="w-4/5">
|
||||
<label className="block font-medium">Code Editor Font</label>
|
||||
<input
|
||||
type="text"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleCodeFontChange}
|
||||
defaultValue={codeFont}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/5">
|
||||
<label className="block font-medium">Font Size</label>
|
||||
<input
|
||||
type="number"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
inputMode="numeric"
|
||||
onChange={handleCodeFontSizeChange}
|
||||
defaultValue={codeFontSize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label className="block font-medium">Code Editor Font</label>
|
||||
<input
|
||||
type="text"
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleInputChange}
|
||||
defaultValue={codeFont}
|
||||
/>
|
||||
|
||||
<div className="mt-10">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.settings-label {
|
||||
width: 100px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
@@ -20,12 +20,6 @@ const StyledWrapper = styled.div`
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.system-proxy-settings {
|
||||
label {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -11,17 +11,14 @@ import { useState } from 'react';
|
||||
|
||||
const ProxySettings = ({ close }) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const systemProxyEnvVariables = useSelector((state) => state.app.systemProxyEnvVariables);
|
||||
const { http_proxy, https_proxy, no_proxy } = systemProxyEnvVariables || {};
|
||||
const dispatch = useDispatch();
|
||||
console.log(preferences);
|
||||
|
||||
const proxySchema = Yup.object({
|
||||
mode: Yup.string().oneOf(['off', 'on', 'system']),
|
||||
enabled: Yup.boolean(),
|
||||
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||
hostname: Yup.string()
|
||||
.when('enabled', {
|
||||
is: 'on',
|
||||
is: true,
|
||||
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
|
||||
otherwise: (hostname) => hostname.nullable()
|
||||
})
|
||||
@@ -34,7 +31,7 @@ const ProxySettings = ({ close }) => {
|
||||
.transform((_, val) => (val ? Number(val) : null)),
|
||||
auth: Yup.object()
|
||||
.when('enabled', {
|
||||
is: 'on',
|
||||
is: true,
|
||||
then: Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
username: Yup.string()
|
||||
@@ -57,7 +54,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
mode: preferences.proxy.mode,
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || 0,
|
||||
@@ -97,7 +94,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
useEffect(() => {
|
||||
formik.setValues({
|
||||
mode: preferences.proxy.mode,
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || '',
|
||||
@@ -112,256 +109,188 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="enabled">
|
||||
Enabled
|
||||
</label>
|
||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Mode
|
||||
Protocol
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="mode"
|
||||
value="false"
|
||||
checked={formik.values.mode === 'off'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('mode', 'off');
|
||||
}}
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
Off
|
||||
</label>
|
||||
<label className="flex items-center ml-4 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="mode"
|
||||
value="true"
|
||||
checked={formik.values.mode === 'on'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('mode', 'on');
|
||||
}}
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
On
|
||||
</label>
|
||||
<label className="flex items-center ml-4 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="mode"
|
||||
value="system"
|
||||
checked={formik.values.mode === 'system'}
|
||||
name="protocol"
|
||||
value="http"
|
||||
checked={formik.values.protocol === 'http'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1 cursor-pointer"
|
||||
className="mr-1"
|
||||
/>
|
||||
System Proxy
|
||||
HTTP
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="https"
|
||||
checked={formik.values.protocol === 'https'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
HTTPS
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks4"
|
||||
checked={formik.values.protocol === 'socks4'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS4
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks5"
|
||||
checked={formik.values.protocol === 'socks5'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS5
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{formik?.values?.mode === 'system' ? (
|
||||
<div className="mb-3 pt-1 text-muted system-proxy-settings">
|
||||
<small>
|
||||
Below values are sourced from your system environment variables and cannot be directly updated in Bruno.<br/>
|
||||
Please refer to your OS documentation to change these values.
|
||||
</small>
|
||||
<div className="flex flex-col justify-start items-start pt-2">
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="http_proxy">
|
||||
http_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{http_proxy || '-'}</div>
|
||||
</div>
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="https_proxy">
|
||||
https_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{https_proxy || '-'}</div>
|
||||
</div>
|
||||
<div className="mb-1 flex items-center">
|
||||
<label className="settings-label" htmlFor="no_proxy">
|
||||
no_proxy
|
||||
</label>
|
||||
<div className="opacity-80">{no_proxy || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
</label>
|
||||
<input
|
||||
id="hostname"
|
||||
type="text"
|
||||
name="hostname"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.hostname || ''}
|
||||
/>
|
||||
{formik.touched.hostname && formik.errors.hostname ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="port">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
id="port"
|
||||
type="number"
|
||||
name="port"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.port}
|
||||
/>
|
||||
{formik.touched.port && formik.errors.port ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.enabled">
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="auth.enabled"
|
||||
checked={formik.values.auth.enabled}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="auth.username"
|
||||
type="text"
|
||||
name="auth.username"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.username}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.username && formik.errors.auth?.username ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{formik?.values?.mode === 'on' ? (
|
||||
<>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Protocol
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="http"
|
||||
checked={formik.values.protocol === 'http'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
HTTP
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="https"
|
||||
checked={formik.values.protocol === 'https'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
HTTPS
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks4"
|
||||
checked={formik.values.protocol === 'socks4'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS4
|
||||
</label>
|
||||
<label className="flex items-center ml-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="socks5"
|
||||
checked={formik.values.protocol === 'socks5'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
SOCKS5
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
</label>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.password">
|
||||
Password
|
||||
</label>
|
||||
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
|
||||
<input
|
||||
id="hostname"
|
||||
type="text"
|
||||
name="hostname"
|
||||
className="block textbox"
|
||||
id="auth.password"
|
||||
type={passwordVisible ? `text` : 'password'}
|
||||
name="auth.password"
|
||||
className="outline-none w-[10.5rem] bg-transparent"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.hostname || ''}
|
||||
/>
|
||||
{formik.touched.hostname && formik.errors.hostname ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="port">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
id="port"
|
||||
type="number"
|
||||
name="port"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.port}
|
||||
/>
|
||||
{formik.touched.port && formik.errors.port ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.port}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.enabled">
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="auth.enabled"
|
||||
checked={formik.values.auth.enabled}
|
||||
value={formik.values.auth.password}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm absolute right-0"
|
||||
onClick={() => setPasswordVisible(!passwordVisible)}
|
||||
>
|
||||
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="auth.username"
|
||||
type="text"
|
||||
name="auth.username"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.username}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.username && formik.errors.auth?.username ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.password">
|
||||
Password
|
||||
</label>
|
||||
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
|
||||
<input
|
||||
id="auth.password"
|
||||
type={passwordVisible ? `text` : 'password'}
|
||||
name="auth.password"
|
||||
className="outline-none w-[10.5rem] bg-transparent"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.password}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm absolute right-0"
|
||||
onClick={() => setPasswordVisible(!passwordVisible)}
|
||||
>
|
||||
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
|
||||
</button>
|
||||
</div>
|
||||
{formik.touched.auth?.password && formik.errors.auth?.password ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="bypassProxy">
|
||||
Proxy Bypass
|
||||
</label>
|
||||
<input
|
||||
id="bypassProxy"
|
||||
type="text"
|
||||
name="bypassProxy"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.bypassProxy || ''}
|
||||
/>
|
||||
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
{formik.touched.auth?.password && formik.errors.auth?.password ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="bypassProxy">
|
||||
Proxy Bypass
|
||||
</label>
|
||||
<input
|
||||
id="bypassProxy"
|
||||
type="text"
|
||||
name="bypassProxy"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.bypassProxy || ''}
|
||||
/>
|
||||
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
|
||||
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
||||
Save
|
||||
|
||||
@@ -31,7 +31,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
: get(item, 'request.body.graphql.variables');
|
||||
const { displayedTheme } = useTheme();
|
||||
const [schema, setSchema] = useState(null);
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
useEffect(() => {
|
||||
onSchemaLoad(schema);
|
||||
@@ -72,8 +71,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
onRun={onRun}
|
||||
onEdit={onQueryChange}
|
||||
onClickReference={handleGqlClickReference}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
value={variables || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -4,8 +4,6 @@ const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
font-family: ${(props) => (props.font ? props.font : 'default')};
|
||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,8 +211,6 @@ export default class QueryEditor extends React.Component {
|
||||
<StyledWrapper
|
||||
className="h-full w-full flex flex-col relative"
|
||||
aria-label="Query Editor"
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
@@ -175,7 +175,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
</button>
|
||||
<div className="mb-2 title text-xs flex items-stretch">
|
||||
<span>Path</span>
|
||||
<InfoTip
|
||||
<Tooltip
|
||||
text={`
|
||||
<div>
|
||||
Path variables are automatically added whenever the
|
||||
@@ -186,7 +186,7 @@ const QueryParams = ({ item, collection }) => {
|
||||
</code>
|
||||
</div>
|
||||
`}
|
||||
infotipId="path-param-InfoTip"
|
||||
tooltipId="path-param-tooltip"
|
||||
/>
|
||||
</div>
|
||||
<table>
|
||||
|
||||
@@ -33,18 +33,18 @@ const Wrapper = styled.div`
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.infotip {
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.infotip:hover .infotiptext {
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.infotiptext {
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
background-color: ${(props) => props.theme.requestTabs.active.bg};
|
||||
@@ -62,7 +62,7 @@ const Wrapper = styled.div`
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.infotiptext::after {
|
||||
.tooltiptext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
|
||||
@@ -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="infotip mr-3"
|
||||
className="tooltip mx-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!item.draft) return;
|
||||
@@ -87,7 +87,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
size={22}
|
||||
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
/>
|
||||
<span className="infotiptext text-xs">
|
||||
<span className="tooltiptext text-xs">
|
||||
Save <span className="shortcut">({saveShortcut})</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,6 @@ const RequestBody = ({ item, collection }) => {
|
||||
collection={collection}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
value={bodyContent[bodyMode] || ''}
|
||||
onEdit={onEdit}
|
||||
onRun={onRun}
|
||||
|
||||
@@ -47,7 +47,6 @@ const Script = ({ item, collection }) => {
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
@@ -61,7 +60,6 @@ const Script = ({ item, collection }) => {
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -34,7 +34,6 @@ const Tests = ({ item, collection }) => {
|
||||
value={tests || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from 'providers/Theme';
|
||||
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
@@ -83,14 +83,14 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
|
||||
@@ -2,4 +2,4 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div``;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -4,7 +4,6 @@ import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
|
||||
|
||||
@@ -52,20 +51,14 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<span className="mr-2">
|
||||
<JsSandboxMode collection={collection} />
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<ToolHint text="Runner" toolhintId="RunnnerToolhintId" place='bottom'>
|
||||
<IconRun className="cursor-pointer" size={18} strokeWidth={1.5} onClick={handleRun} />
|
||||
</ToolHint>
|
||||
<span className="mr-2">
|
||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<ToolHint text="Variables" toolhintId="VariablesToolhintId">
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
</ToolHint>
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<ToolHint text="Collection Settings" toolhintId="CollectionSettingsToolhintId">
|
||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||
</ToolHint>
|
||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||
</span>
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IconFilter, IconX } from '@tabler/icons';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Tooltip as ReactInfotip } from 'react-tooltip';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
|
||||
const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
const inputRef = useRef(null);
|
||||
@@ -19,7 +19,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const infotipText = useMemo(() => {
|
||||
const tooltipText = useMemo(() => {
|
||||
if (mode.includes('json')) {
|
||||
return 'Filter with JSONPath';
|
||||
}
|
||||
@@ -49,7 +49,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
|
||||
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2 pointer-events-none'
|
||||
}
|
||||
>
|
||||
{infotipText && !isExpanded && <ReactInfotip anchorId={'request-filter-icon'} html={infotipText} />}
|
||||
{tooltipText && !isExpanded && <ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
|
||||
@@ -81,7 +81,6 @@ const QueryResultPreview = ({
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
theme={displayedTheme}
|
||||
onRun={onRun}
|
||||
value={formattedData}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as Yup from 'yup';
|
||||
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { cloneCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
const CloneCollection = ({ onClose, collection }) => {
|
||||
@@ -126,9 +126,9 @@ const CloneCollection = ({ onClose, collection }) => {
|
||||
|
||||
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
|
||||
<span className="font-semibold">Folder Name</span>
|
||||
<InfoTip
|
||||
<Tooltip
|
||||
text="This folder will be created under the selected location"
|
||||
infotipId="collection-folder-name-infotip"
|
||||
tooltipId="collection-folder-name-tooltip"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -53,7 +53,6 @@ const CodeView = ({ language, item }) => {
|
||||
collection={collection}
|
||||
value={snippet}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
theme={displayedTheme}
|
||||
mode={lang}
|
||||
/>
|
||||
|
||||
@@ -65,7 +65,7 @@ const Wrapper = styled.div`
|
||||
div.dropdown-item.delete-item {
|
||||
color: ${(props) => props.theme.colors.danger};
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.colors.bg.danger} !important;
|
||||
background-color: ${(props) => props.theme.colors.bg.danger};
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as Yup from 'yup';
|
||||
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import toast from 'react-hot-toast';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
const CreateCollection = ({ onClose }) => {
|
||||
@@ -119,9 +119,9 @@ const CreateCollection = ({ onClose }) => {
|
||||
|
||||
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
|
||||
<span className="font-semibold">Folder Name</span>
|
||||
<InfoTip
|
||||
<Tooltip
|
||||
text="This folder will be created under the selected location"
|
||||
infotipId="collection-folder-name-infotip"
|
||||
tooltipId="collection-folder-name-tooltip"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -4,7 +4,6 @@ import StyledWrapper from './StyledWrapper';
|
||||
import GitHubButton from 'react-github-btn';
|
||||
import Preferences from 'components/Preferences';
|
||||
import Cookies from 'components/Cookies';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import GoldenEdition from './GoldenEdition';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
@@ -96,30 +95,28 @@ const Sidebar = () => {
|
||||
|
||||
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center select-none">
|
||||
<div className="flex items-center ml-1 text-xs ">
|
||||
<a className="mr-2 cursor-pointer" onClick={() => dispatch(showPreferences(true))}>
|
||||
<ToolHint text="Preferences" toolhintId="Preferences" effect='float' place='top-start' offset={8}>
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</ToolHint>
|
||||
<a
|
||||
title="Preferences"
|
||||
className="mr-2 cursor-pointer hover:text-gray-700"
|
||||
onClick={() => dispatch(showPreferences(true))}
|
||||
>
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</a>
|
||||
<a
|
||||
className="mr-2 cursor-pointer"
|
||||
title="Cookies"
|
||||
className="mr-2 cursor-pointer hover:text-gray-700"
|
||||
onClick={() => setCookiesOpen(true)}
|
||||
>
|
||||
<ToolHint text="Cookies" toolhintId="Cookies" offset={8}>
|
||||
<IconCookie size={18} strokeWidth={1.5} />
|
||||
</ToolHint>
|
||||
<IconCookie size={18} strokeWidth={1.5} />
|
||||
</a>
|
||||
<a
|
||||
className="mr-2 cursor-pointer"
|
||||
title="Golden Edition"
|
||||
className="mr-2 cursor-pointer hover:text-gray-700"
|
||||
onClick={() => setGoldenEditonOpen(true)}
|
||||
>
|
||||
<ToolHint text="Golden Edition" toolhintId="Golden Edition" offset={8} >
|
||||
<IconHeart size={18} strokeWidth={1.5} />
|
||||
</ToolHint>
|
||||
</a>
|
||||
<a>
|
||||
<Notifications />
|
||||
<IconHeart size={18} strokeWidth={1.5} />
|
||||
</a>
|
||||
<Notifications />
|
||||
</div>
|
||||
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
|
||||
{/* This will get moved to home page */}
|
||||
@@ -129,10 +126,10 @@ const Sidebar = () => {
|
||||
data-show-count="true"
|
||||
aria-label="Star usebruno/bruno on GitHub"
|
||||
>
|
||||
Star
|
||||
Star
|
||||
</GitHubButton> */}
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.27.0</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.26.2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,7 +137,7 @@ const Sidebar = () => {
|
||||
<div className="absolute drag-sidebar h-full" onMouseDown={handleDragbarMouseDown}>
|
||||
<div className="drag-request-border" />
|
||||
</div>
|
||||
</StyledWrapper >
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${(props) => props.theme.sidebar.badge};
|
||||
color: ${(props) => props.theme.text};
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Tooltip as ReactToolHint } from 'react-tooltip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const ToolHint = ({
|
||||
text,
|
||||
toolhintId,
|
||||
children,
|
||||
tooltipStyle = {},
|
||||
place = 'top',
|
||||
offset,
|
||||
theme = null
|
||||
}) => {
|
||||
const { theme: contextTheme } = useTheme();
|
||||
const appliedTheme = theme || contextTheme;
|
||||
|
||||
const toolhintBackgroundColor = appliedTheme?.sidebar.badge.bg || 'black';
|
||||
const toolhintTextColor = appliedTheme?.text || 'white';
|
||||
|
||||
const combinedToolhintStyle = {
|
||||
...tooltipStyle,
|
||||
fontSize: '0.75rem',
|
||||
padding: '0.25rem 0.5rem',
|
||||
backgroundColor: toolhintBackgroundColor,
|
||||
color: toolhintTextColor
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<span id={toolhintId}>{children}</span>
|
||||
<StyledWrapper theme={appliedTheme}>
|
||||
<ReactToolHint
|
||||
anchorId={toolhintId}
|
||||
html={text}
|
||||
className="toolhint"
|
||||
offset={offset}
|
||||
place={place}
|
||||
noArrow={true}
|
||||
style={combinedToolhintStyle}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToolHint;
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Tooltip as ReactInfoTip } from 'react-tooltip';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
|
||||
const InfoTip = ({ text, infotipId }) => {
|
||||
const Tooltip = ({ text, tooltipId }) => {
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
tabIndex="-1"
|
||||
id={infotipId}
|
||||
id={tooltipId}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -17,9 +17,9 @@ const InfoTip = ({ text, infotipId }) => {
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
|
||||
</svg>
|
||||
<ReactInfoTip anchorId={infotipId} html={text} />
|
||||
<ReactTooltip anchorId={tooltipId} html={text} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoTip;
|
||||
export default Tooltip;
|
||||
@@ -1,10 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
showPreferences,
|
||||
updateCookies,
|
||||
updatePreferences,
|
||||
updateSystemProxyEnvVariables
|
||||
} from 'providers/ReduxStore/slices/app';
|
||||
import { showPreferences, updateCookies, updatePreferences } from 'providers/ReduxStore/slices/app';
|
||||
import {
|
||||
brunoConfigUpdateEvent,
|
||||
collectionAddDirectoryEvent,
|
||||
@@ -141,10 +136,6 @@ const useIpcEvents = () => {
|
||||
dispatch(updatePreferences(val));
|
||||
});
|
||||
|
||||
const removeSystemProxyEnvUpdatesListener = ipcRenderer.on('main:load-system-proxy-env', (val) => {
|
||||
dispatch(updateSystemProxyEnvVariables(val));
|
||||
});
|
||||
|
||||
const removeCookieUpdateListener = ipcRenderer.on('main:cookies-update', (val) => {
|
||||
dispatch(updateCookies(val));
|
||||
});
|
||||
@@ -164,7 +155,6 @@ const useIpcEvents = () => {
|
||||
removeShowPreferencesListener();
|
||||
removePreferencesUpdatesListener();
|
||||
removeCookieUpdateListener();
|
||||
removeSystemProxyEnvUpdatesListener();
|
||||
};
|
||||
}, [isElectron]);
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ const trackStart = () => {
|
||||
event: 'start',
|
||||
properties: {
|
||||
os: platformLib.os.family,
|
||||
version: '1.27.0'
|
||||
version: '1.26.2'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import NetworkError from 'components/ResponsePane/NetworkError';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
|
||||
import { closeTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
|
||||
export const HotkeysContext = React.createContext();
|
||||
|
||||
@@ -154,41 +154,7 @@ export const HotkeysProvider = (props) => {
|
||||
};
|
||||
}, [activeTabUid]);
|
||||
|
||||
// Switch to the previous tab
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+pageup', 'ctrl+pageup'], (e) => {
|
||||
dispatch(
|
||||
switchTab({
|
||||
direction: 'pageup'
|
||||
})
|
||||
);
|
||||
|
||||
return false; // this stops the event bubbling
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind(['command+pageup', 'ctrl+pageup']);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// Switch to the next tab
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+pagedown', 'ctrl+pagedown'], (e) => {
|
||||
dispatch(
|
||||
switchTab({
|
||||
direction: 'pagedown'
|
||||
})
|
||||
);
|
||||
|
||||
return false; // this stops the event bubbling
|
||||
});
|
||||
|
||||
return () => {
|
||||
Mousetrap.unbind(['command+pagedown', 'ctrl+pagedown']);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// Close all tabs
|
||||
// close all tabs
|
||||
useEffect(() => {
|
||||
Mousetrap.bind(['command+shift+w', 'ctrl+shift+w'], (e) => {
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
|
||||
@@ -27,8 +27,7 @@ const initialState = {
|
||||
}
|
||||
},
|
||||
cookies: [],
|
||||
taskQueue: [],
|
||||
systemProxyEnvVariables: {}
|
||||
taskQueue: []
|
||||
};
|
||||
|
||||
export const appSlice = createSlice({
|
||||
@@ -73,9 +72,6 @@ export const appSlice = createSlice({
|
||||
},
|
||||
removeAllTasksFromQueue: (state) => {
|
||||
state.taskQueue = [];
|
||||
},
|
||||
updateSystemProxyEnvVariables: (state, action) => {
|
||||
state.systemProxyEnvVariables = action.payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -93,8 +89,7 @@ export const {
|
||||
updateCookies,
|
||||
insertTaskIntoQueue,
|
||||
removeTaskFromQueue,
|
||||
removeAllTasksFromQueue,
|
||||
updateSystemProxyEnvVariables
|
||||
removeAllTasksFromQueue
|
||||
} = appSlice.actions;
|
||||
|
||||
export const savePreferences = (preferences) => (dispatch, getState) => {
|
||||
|
||||
@@ -48,26 +48,6 @@ export const tabsSlice = createSlice({
|
||||
focusTab: (state, action) => {
|
||||
state.activeTabUid = action.payload.uid;
|
||||
},
|
||||
switchTab: (state, action) => {
|
||||
if (!state.tabs || !state.tabs.length) {
|
||||
state.activeTabUid = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = action.payload.direction;
|
||||
|
||||
const activeTabIndex = state.tabs.findIndex((t) => t.uid === state.activeTabUid);
|
||||
|
||||
let toBeActivatedTabIndex = 0;
|
||||
|
||||
if (direction == 'pageup') {
|
||||
toBeActivatedTabIndex = (activeTabIndex - 1 + state.tabs.length) % state.tabs.length;
|
||||
} else if (direction == 'pagedown') {
|
||||
toBeActivatedTabIndex = (activeTabIndex + 1) % state.tabs.length;
|
||||
}
|
||||
|
||||
state.activeTabUid = state.tabs[toBeActivatedTabIndex].uid;
|
||||
},
|
||||
updateRequestPaneTabWidth: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
|
||||
@@ -131,7 +111,6 @@ export const tabsSlice = createSlice({
|
||||
export const {
|
||||
addTab,
|
||||
focusTab,
|
||||
switchTab,
|
||||
updateRequestPaneTabWidth,
|
||||
updateRequestPaneTab,
|
||||
updateResponsePaneTab,
|
||||
|
||||
@@ -149,9 +149,7 @@ export const relativeDate = (dateString) => {
|
||||
};
|
||||
|
||||
export const humanizeDate = (dateString) => {
|
||||
// See this discussion for why .split is necessary
|
||||
// https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off
|
||||
const date = new Date(dateString.split('-'));
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { describe, it, expect } = require('@jest/globals');
|
||||
|
||||
import { normalizeFileName, startsWith, humanizeDate, relativeDate } from './index';
|
||||
import { normalizeFileName, startsWith } from './index';
|
||||
|
||||
describe('common utils', () => {
|
||||
describe('normalizeFileName', () => {
|
||||
@@ -49,50 +49,4 @@ describe('common utils', () => {
|
||||
expect(startsWith('foo', 'foo')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('humanizeDate', () => {
|
||||
it('should return a date string in the en-US locale', () => {
|
||||
expect(humanizeDate('2024-03-17')).toBe('March 17, 2024');
|
||||
});
|
||||
|
||||
it('should return invalid date if the date is invalid', () => {
|
||||
expect(humanizeDate('9999-99-99')).toBe('Invalid Date');
|
||||
});
|
||||
});
|
||||
|
||||
describe('relativeDate', () => {
|
||||
it('should return few seconds ago', () => {
|
||||
expect(relativeDate(new Date())).toBe('Few seconds ago');
|
||||
});
|
||||
|
||||
it('should return minutes ago', () => {
|
||||
let date = new Date();
|
||||
date.setMinutes(date.getMinutes() - 30);
|
||||
expect(relativeDate(date)).toBe('30 minutes ago');
|
||||
});
|
||||
|
||||
it('should return hours ago', () => {
|
||||
let date = new Date();
|
||||
date.setHours(date.getHours() - 10);
|
||||
expect(relativeDate(date)).toBe('10 hours ago');
|
||||
});
|
||||
|
||||
it('should return days ago', () => {
|
||||
let date = new Date();
|
||||
date.setDate(date.getDate() - 5);
|
||||
expect(relativeDate(date)).toBe('5 days ago');
|
||||
});
|
||||
|
||||
it('should return weeks ago', () => {
|
||||
let date = new Date();
|
||||
date.setDate(date.getDate() - 8);
|
||||
expect(relativeDate(date)).toBe('1 week ago');
|
||||
});
|
||||
|
||||
it('should return months ago', () => {
|
||||
let date = new Date();
|
||||
date.setDate(date.getDate() - 60);
|
||||
expect(relativeDate(date)).toBe('2 months ago');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,17 +18,16 @@ const hasLength = (str) => {
|
||||
};
|
||||
|
||||
export const parseQueryParams = (query) => {
|
||||
try {
|
||||
if (!query || !query.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(new URLSearchParams(query.split('#')[0]).entries())
|
||||
.map(([name, value]) => ({ name, value }));
|
||||
} catch (error) {
|
||||
console.error('Error parsing query params:', error);
|
||||
if (!query || !query.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let params = query.split('&').map((param) => {
|
||||
let [name, value = ''] = param.split('=');
|
||||
return { name, value };
|
||||
});
|
||||
|
||||
return filter(params, (p) => hasLength(p.name));
|
||||
};
|
||||
|
||||
export const parsePathParams = (url) => {
|
||||
|
||||
@@ -49,23 +49,6 @@ describe('Url Utils - parseQueryParams', () => {
|
||||
{ name: 'b', value: '2' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse query with "=" character - case 9', () => {
|
||||
const params = parseQueryParams('a=1&b={color=red,size=large}&c=3');
|
||||
expect(params).toEqual([
|
||||
{ name: 'a', value: '1' },
|
||||
{ name: 'b', value: '{color=red,size=large}' },
|
||||
{ name: 'c', value: '3' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse query with fragment - case 10', () => {
|
||||
const params = parseQueryParams('a=1&b=2#I-AM-FRAGMENT');
|
||||
expect(params).toEqual([
|
||||
{ name: 'a', value: '1' },
|
||||
{ name: 'b', value: '2' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Url Utils - parsePathParams', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "v1.27.0",
|
||||
"version": "v1.26.2",
|
||||
"name": "bruno",
|
||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||
"homepage": "https://www.usebruno.com",
|
||||
|
||||
@@ -3,7 +3,7 @@ const path = require('path');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
if (isDev) {
|
||||
if(!fs.existsSync(path.join(__dirname, '../../bruno-js/src/sandbox/bundle-browser-rollup.js'))) {
|
||||
if (!fs.existsSync('./src/sandbox/bundle-browser-rollup.js')) {
|
||||
console.log('JS Sandbox libraries have not been bundled yet');
|
||||
console.log('Please run the below command \nnpm run sandbox:bundle-libraries --workspace=packages/bruno-js');
|
||||
throw new Error('JS Sandbox libraries have not been bundled yet');
|
||||
|
||||
@@ -49,9 +49,7 @@ const checkConnection = (host, port) =>
|
||||
*/
|
||||
function makeAxiosInstance() {
|
||||
/** @type {axios.AxiosInstance} */
|
||||
const instance = axios.create({
|
||||
proxy: false
|
||||
});
|
||||
const instance = axios.create();
|
||||
|
||||
instance.interceptors.request.use(async (config) => {
|
||||
const url = URL.parse(config.url);
|
||||
|
||||
@@ -165,92 +165,51 @@ const configureRequest = async (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy configuration
|
||||
*
|
||||
* Preferences proxyMode has three possible values: on, off, system
|
||||
* Collection proxyMode has three possible values: true, false, global
|
||||
*
|
||||
* When collection proxyMode is true, it overrides the app-level proxy settings
|
||||
* When collection proxyMode is false, it ignores the app-level proxy settings
|
||||
* When collection proxyMode is global, it uses the app-level proxy settings
|
||||
*
|
||||
* Below logic calculates the proxyMode and proxyConfig to be used for the request
|
||||
*/
|
||||
let proxyMode = 'off';
|
||||
let proxyConfig = {};
|
||||
|
||||
const collectionProxyConfig = get(brunoConfig, 'proxy', {});
|
||||
const collectionProxyEnabled = get(collectionProxyConfig, 'enabled', 'global');
|
||||
if (collectionProxyEnabled === true) {
|
||||
proxyConfig = collectionProxyConfig;
|
||||
proxyMode = 'on';
|
||||
} else if (collectionProxyEnabled === 'global') {
|
||||
// proxy configuration
|
||||
let proxyConfig = get(brunoConfig, 'proxy', {});
|
||||
let proxyEnabled = get(proxyConfig, 'enabled', 'global');
|
||||
if (proxyEnabled === 'global') {
|
||||
proxyConfig = preferencesUtil.getGlobalProxyConfig();
|
||||
proxyMode = get(proxyConfig, 'mode', 'off');
|
||||
proxyEnabled = get(proxyConfig, 'enabled', false);
|
||||
}
|
||||
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
|
||||
if (proxyEnabled === true && shouldProxy) {
|
||||
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
|
||||
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
|
||||
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
|
||||
const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false);
|
||||
const socksEnabled = proxyProtocol.includes('socks');
|
||||
|
||||
if (proxyMode === 'on') {
|
||||
const shouldProxy = shouldUseProxy(request.url, get(proxyConfig, 'bypassProxy', ''));
|
||||
if (shouldProxy) {
|
||||
const proxyProtocol = interpolateString(get(proxyConfig, 'protocol'), interpolationOptions);
|
||||
const proxyHostname = interpolateString(get(proxyConfig, 'hostname'), interpolationOptions);
|
||||
const proxyPort = interpolateString(get(proxyConfig, 'port'), interpolationOptions);
|
||||
const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false);
|
||||
const socksEnabled = proxyProtocol.includes('socks');
|
||||
let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`;
|
||||
let proxyUri;
|
||||
if (proxyAuthEnabled) {
|
||||
const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions);
|
||||
const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions);
|
||||
let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`;
|
||||
let proxyUri;
|
||||
if (proxyAuthEnabled) {
|
||||
const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions);
|
||||
const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions);
|
||||
|
||||
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`;
|
||||
} else {
|
||||
proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`;
|
||||
}
|
||||
if (socksEnabled) {
|
||||
request.httpsAgent = new SocksProxyAgent(
|
||||
proxyUri,
|
||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||
);
|
||||
request.httpAgent = new SocksProxyAgent(proxyUri);
|
||||
} else {
|
||||
request.httpsAgent = new PatchedHttpsProxyAgent(
|
||||
proxyUri,
|
||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||
);
|
||||
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||
}
|
||||
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`;
|
||||
} else {
|
||||
proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`;
|
||||
}
|
||||
} else if (proxyMode === 'system') {
|
||||
const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables();
|
||||
const shouldUseSystemProxy = shouldUseProxy(request.url, no_proxy || '');
|
||||
if (shouldUseSystemProxy) {
|
||||
try {
|
||||
if (http_proxy?.length) {
|
||||
new URL(http_proxy);
|
||||
request.httpAgent = new HttpProxyAgent(http_proxy);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Invalid system http_proxy');
|
||||
}
|
||||
try {
|
||||
if (https_proxy?.length) {
|
||||
new URL(https_proxy);
|
||||
request.httpsAgent = new PatchedHttpsProxyAgent(
|
||||
https_proxy,
|
||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Invalid system https_proxy');
|
||||
}
|
||||
|
||||
if (socksEnabled) {
|
||||
request.httpsAgent = new SocksProxyAgent(
|
||||
proxyUri,
|
||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||
);
|
||||
request.httpAgent = new SocksProxyAgent(proxyUri);
|
||||
} else {
|
||||
request.httpsAgent = new PatchedHttpsProxyAgent(
|
||||
proxyUri,
|
||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||
);
|
||||
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||
}
|
||||
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
||||
request.httpsAgent = new https.Agent({
|
||||
...httpsAgentRequestFields
|
||||
});
|
||||
}
|
||||
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
|
||||
if (request.oauth2) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { ipcMain } = require('electron');
|
||||
const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
|
||||
const { getPreferences, savePreferences } = require('../store/preferences');
|
||||
const { isDirectory } = require('../utils/filesystem');
|
||||
const { openCollection } = require('../app/collections');
|
||||
``;
|
||||
@@ -9,10 +9,6 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
||||
const preferences = getPreferences();
|
||||
mainWindow.webContents.send('main:load-preferences', preferences);
|
||||
|
||||
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
|
||||
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
|
||||
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
|
||||
|
||||
// reload last opened collections
|
||||
const lastOpened = lastOpenedCollections.getAll();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Yup = require('yup');
|
||||
const Store = require('electron-store');
|
||||
const { get, merge } = require('lodash');
|
||||
const { get } = require('lodash');
|
||||
|
||||
/**
|
||||
* The preferences are stored in the electron store 'preferences.json'.
|
||||
@@ -23,11 +23,10 @@ const defaultPreferences = {
|
||||
timeout: 0
|
||||
},
|
||||
font: {
|
||||
codeFont: 'default',
|
||||
codeFontSize: 14
|
||||
codeFont: 'default'
|
||||
},
|
||||
proxy: {
|
||||
mode: 'off',
|
||||
enabled: false,
|
||||
protocol: 'http',
|
||||
hostname: '',
|
||||
port: null,
|
||||
@@ -55,11 +54,10 @@ const preferencesSchema = Yup.object().shape({
|
||||
timeout: Yup.number()
|
||||
}),
|
||||
font: Yup.object().shape({
|
||||
codeFont: Yup.string().nullable(),
|
||||
codeFontSize: Yup.number().min(1).max(32).nullable()
|
||||
codeFont: Yup.string().nullable()
|
||||
}),
|
||||
proxy: Yup.object({
|
||||
mode: Yup.string().oneOf(['off', 'on', 'system']),
|
||||
enabled: Yup.boolean(),
|
||||
protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||
hostname: Yup.string().max(1024),
|
||||
port: Yup.number().min(1).max(65535).nullable(),
|
||||
@@ -81,22 +79,10 @@ class PreferencesStore {
|
||||
}
|
||||
|
||||
getPreferences() {
|
||||
let preferences = this.store.get('preferences', {});
|
||||
|
||||
// This to support the old preferences format
|
||||
// In the old format, we had a proxy.enabled flag
|
||||
// In the new format, this maps to proxy.mode = 'on'
|
||||
if (preferences?.proxy?.enabled) {
|
||||
preferences.proxy.mode = 'on';
|
||||
}
|
||||
|
||||
// Delete the proxy.enabled property if it exists, regardless of its value
|
||||
// This is a part of migration to the new preferences format
|
||||
if (preferences?.proxy && 'enabled' in preferences.proxy) {
|
||||
delete preferences.proxy.enabled;
|
||||
}
|
||||
|
||||
return merge({}, defaultPreferences, preferences);
|
||||
return {
|
||||
...defaultPreferences,
|
||||
...this.store.get('preferences')
|
||||
};
|
||||
}
|
||||
|
||||
savePreferences(newPreferences) {
|
||||
@@ -148,14 +134,6 @@ const preferencesUtil = {
|
||||
},
|
||||
shouldSendCookies: () => {
|
||||
return get(getPreferences(), 'request.sendCookies', true);
|
||||
},
|
||||
getSystemProxyEnvVariables: () => {
|
||||
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
|
||||
return {
|
||||
http_proxy: http_proxy || HTTP_PROXY,
|
||||
https_proxy: https_proxy || HTTPS_PROXY,
|
||||
no_proxy: no_proxy || NO_PROXY
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ auth:bearer {
|
||||
|
||||
body:json {
|
||||
{
|
||||
"hello": 990531470713421825,
|
||||
"decimal": 1.0,
|
||||
"decimal2": 1.00,
|
||||
"decimal3": 1.00200,
|
||||
"decimal4": 0.00
|
||||
"hello": 990531470713421825
|
||||
}
|
||||
}
|
||||
|
||||
body:text {
|
||||
{
|
||||
"hello": 990531470713421825
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +39,6 @@ assert {
|
||||
res.status: eq 200
|
||||
}
|
||||
|
||||
tests {
|
||||
// todo: add tests once lossless json echo server is ready
|
||||
script:pre-request {
|
||||
bru.setVar("foo", "foo-world-2");
|
||||
}
|
||||
|
||||
48
readme.md
48
readme.md
@@ -43,34 +43,14 @@ Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We v
|
||||
|
||||
 <br /><br />
|
||||
|
||||
## Golden Edition ✨
|
||||
### Golden Edition ✨
|
||||
|
||||
Majority of our features are free and open source.
|
||||
We strive to strike a harmonious balance between [open-source principles and sustainability](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
You can buy the [Golden Edition](https://www.usebruno.com/pricing) for a one-time payment of **$19**! <br/>
|
||||
|
||||
## Table of Contents
|
||||
- [Installation](#installation)
|
||||
- [Features](#features)
|
||||
- [Run across multiple platforms 🖥️](#run-across-multiple-platforms-%EF%B8%8F)
|
||||
- [Collaborate via Git 👩💻🧑💻](#collaborate-via-git-)
|
||||
- [Sponsors](#sponsors)
|
||||
- [Gold Sponsors](#gold-sponsors)
|
||||
- [Silver Sponsors](#silver-sponsors)
|
||||
- [Bronze Sponsors](#bronze-sponsors)
|
||||
- [Important Links 📌](#important-links-)
|
||||
- [Showcase 🎥](#showcase-)
|
||||
- [Support ❤️](#support-%EF%B8%8F)
|
||||
- [Share Testimonials 📣](#share-testimonials-)
|
||||
- [Publishing to New Package Managers](#publishing-to-new-package-managers)
|
||||
- [Stay in touch 🌐](#stay-in-touch-)
|
||||
- [Trademark](#trademark)
|
||||
- [Contribute 👩💻🧑💻](#contribute-)
|
||||
- [Authors](#authors)
|
||||
- [License 📄](#license-)
|
||||
|
||||
## Installation
|
||||
### Installation
|
||||
|
||||
Bruno is available as binary download [on our website](https://www.usebruno.com/downloads) for Mac, Windows and Linux.
|
||||
|
||||
@@ -106,8 +86,6 @@ sudo apt update
|
||||
sudo apt install bruno
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Run across multiple platforms 🖥️
|
||||
|
||||
 <br /><br />
|
||||
@@ -118,7 +96,7 @@ Or any version control system of your choice
|
||||
|
||||
 <br /><br />
|
||||
|
||||
## Sponsors
|
||||
### Sponsors
|
||||
|
||||
#### Gold Sponsors
|
||||
|
||||
@@ -134,7 +112,7 @@ Or any version control system of your choice
|
||||
<img src="assets/images/sponsors/zuplo.png" width="120"/>
|
||||
</a>
|
||||
|
||||
## Important Links 📌
|
||||
### Important Links 📌
|
||||
|
||||
- [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269)
|
||||
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
|
||||
@@ -145,32 +123,32 @@ Or any version control system of your choice
|
||||
- [Download](https://www.usebruno.com/downloads)
|
||||
- [GitHub Sponsors](https://github.com/sponsors/helloanoop).
|
||||
|
||||
## Showcase 🎥
|
||||
### Showcase 🎥
|
||||
|
||||
- [Testimonials](https://github.com/usebruno/bruno/discussions/343)
|
||||
- [Knowledge Hub](https://github.com/usebruno/bruno/discussions/386)
|
||||
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||
|
||||
## Support ❤️
|
||||
### Support ❤️
|
||||
|
||||
If you like Bruno and want to support our opensource work, consider sponsoring us via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
|
||||
|
||||
## Share Testimonials 📣
|
||||
### Share Testimonials 📣
|
||||
|
||||
If Bruno has helped you at work and your teams, please don't forget to share your [testimonials on our GitHub discussion](https://github.com/usebruno/bruno/discussions/343)
|
||||
|
||||
## Publishing to New Package Managers
|
||||
### Publishing to New Package Managers
|
||||
|
||||
Please see [here](publishing.md) for more information.
|
||||
|
||||
## Stay in touch 🌐
|
||||
### Stay in touch 🌐
|
||||
|
||||
[𝕏 (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)
|
||||
|
||||
## Trademark
|
||||
### Trademark
|
||||
|
||||
**Name**
|
||||
|
||||
@@ -180,13 +158,13 @@ Please see [here](publishing.md) for more information.
|
||||
|
||||
The logo is sourced from [OpenMoji](https://openmoji.org/library/emoji-1F436/). License: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
## Contribute 👩💻🧑💻
|
||||
### Contribute 👩💻🧑💻
|
||||
|
||||
I am happy that you are looking to improve bruno. Please check out the [contributing guide](contributing.md)
|
||||
|
||||
Even if you are not able to make contributions via code, please don't hesitate to file bugs and feature requests that needs to be implemented to solve your use case.
|
||||
|
||||
## Authors
|
||||
### Authors
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||
@@ -194,6 +172,6 @@ Even if you are not able to make contributions via code, please don't hesitate t
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## License 📄
|
||||
### License 📄
|
||||
|
||||
[MIT](license.md)
|
||||
|
||||
Reference in New Issue
Block a user