mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 01:48:33 +00:00
Compare commits
7 Commits
feat/colle
...
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
|
||||
|
||||
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@@ -50,7 +50,6 @@
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@@ -671,7 +670,6 @@
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -679,7 +677,6 @@
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
@@ -743,7 +740,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.25.2",
|
||||
@@ -758,7 +754,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
@@ -766,7 +761,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
@@ -845,7 +839,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.25.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.24.7",
|
||||
@@ -912,7 +905,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.24.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.24.7",
|
||||
@@ -950,7 +942,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.24.8",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -971,7 +962,6 @@
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.25.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.0",
|
||||
@@ -3654,6 +3644,37 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@n8n/vm2": {
|
||||
"version": "3.9.25",
|
||||
"resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz",
|
||||
"integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.7.0",
|
||||
"acorn-walk": "^8.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"vm2": "bin/vm2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.10",
|
||||
"pnpm": ">=9.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@n8n/vm2/node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "12.3.3",
|
||||
"license": "MIT"
|
||||
@@ -4730,7 +4751,6 @@
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
@@ -4739,7 +4759,6 @@
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "12.2.3",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "*",
|
||||
@@ -4748,7 +4767,6 @@
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
@@ -6222,7 +6240,6 @@
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.23.3",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -7221,7 +7238,6 @@
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
@@ -8461,7 +8477,6 @@
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.11",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-util": {
|
||||
@@ -9423,7 +9438,6 @@
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -13172,7 +13186,6 @@
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-vault": {
|
||||
@@ -16188,7 +16201,6 @@
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -17651,7 +17663,6 @@
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -18622,7 +18633,7 @@
|
||||
},
|
||||
"packages/bruno-electron": {
|
||||
"name": "bruno",
|
||||
"version": "v1.27.0",
|
||||
"version": "v1.26.1",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.525.0",
|
||||
"@usebruno/common": "0.1.0",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.title {
|
||||
color: var(--color-tab-inactive);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,56 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-var {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
background-color: inherit;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,162 +0,0 @@
|
||||
import React from 'react';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
import {
|
||||
addCollectionVar,
|
||||
deleteCollectionVar,
|
||||
updateCollectionVar
|
||||
} from 'providers/ReduxStore/slices/collections/index';
|
||||
|
||||
const VarsTable = ({ collection, vars, varType }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const addVar = () => {
|
||||
dispatch(
|
||||
addCollectionVar({
|
||||
collectionUid: collection.uid,
|
||||
type: varType
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
const handleVarChange = (e, v, type) => {
|
||||
const _var = cloneDeep(v);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
const value = e.target.value;
|
||||
|
||||
if (variableNameRegex.test(value) === false) {
|
||||
toast.error(
|
||||
'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_var.name = value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
_var.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
_var.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(
|
||||
updateCollectionVar({
|
||||
type: varType,
|
||||
var: _var,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveVar = (_var) => {
|
||||
dispatch(
|
||||
deleteCollectionVar({
|
||||
type: varType,
|
||||
varUid: _var.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
{varType === 'request' ? (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vars && vars.length
|
||||
? vars.map((_var) => {
|
||||
return (
|
||||
<tr key={_var.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={_var.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, _var, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={_var.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleVarChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
_var,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={_var.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, _var, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={addVar}>
|
||||
+ Add
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default VarsTable;
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import VarsTable from './VarsTable';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const Vars = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const requestVars = get(collection, 'root.request.vars.req', []);
|
||||
const responseVars = get(collection, 'root.request.vars.res', []);
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<VarsTable collection={collection} vars={requestVars} varType="request" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<VarsTable collection={collection} vars={responseVars} varType="response" />
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Vars;
|
||||
@@ -16,7 +16,6 @@ import Docs from './Docs';
|
||||
import Presets from './Presets';
|
||||
import Info from './Info';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars/index';
|
||||
|
||||
const CollectionSettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -78,9 +77,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'headers': {
|
||||
return <Headers collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth collection={collection} />;
|
||||
}
|
||||
@@ -99,7 +95,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'clientCert': {
|
||||
return (
|
||||
<ClientCertSettings
|
||||
root={collection.pathname}
|
||||
clientCertConfig={clientCertConfig}
|
||||
onUpdate={onClientCertSettingsUpdate}
|
||||
onRemove={onClientCertSettingsRemove}
|
||||
@@ -127,9 +122,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
|
||||
Vars
|
||||
</div>
|
||||
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||
Auth
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -116,7 +116,6 @@ const Headers = ({ collection, folder }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
@@ -130,7 +130,6 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<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}>
|
||||
|
||||
@@ -191,7 +191,6 @@ const AssertionRow = ({
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
) : (
|
||||
<input type="text" className="cursor-default" disabled />
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
@@ -132,7 +132,6 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</td>
|
||||
<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;
|
||||
@@ -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);
|
||||
|
||||
@@ -1302,71 +1302,6 @@ export const collectionsSlice = createSlice({
|
||||
set(collection, 'root.request.headers', headers);
|
||||
}
|
||||
},
|
||||
addCollectionVar: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const type = action.payload.type;
|
||||
if (collection) {
|
||||
if (type === 'request') {
|
||||
const vars = get(collection, 'root.request.vars.req', []);
|
||||
vars.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
enabled: true
|
||||
});
|
||||
set(collection, 'root.request.vars.req', vars);
|
||||
} else if (type === 'response') {
|
||||
const vars = get(collection, 'root.request.vars.res', []);
|
||||
vars.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
enabled: true
|
||||
});
|
||||
set(collection, 'root.request.vars.res', vars);
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCollectionVar: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const type = action.payload.type;
|
||||
if (type === 'request') {
|
||||
let vars = get(collection, 'root.request.vars.req', []);
|
||||
const _var = find(vars, (h) => h.uid === action.payload.var.uid);
|
||||
if (_var) {
|
||||
_var.name = action.payload.var.name;
|
||||
_var.value = action.payload.var.value;
|
||||
_var.description = action.payload.var.description;
|
||||
_var.enabled = action.payload.var.enabled;
|
||||
}
|
||||
set(collection, 'root.request.vars.req', vars);
|
||||
} else if (type === 'response') {
|
||||
let vars = get(collection, 'root.request.vars.res', []);
|
||||
const _var = find(vars, (h) => h.uid === action.payload.var.uid);
|
||||
if (_var) {
|
||||
_var.name = action.payload.var.name;
|
||||
_var.value = action.payload.var.value;
|
||||
_var.description = action.payload.var.description;
|
||||
_var.enabled = action.payload.var.enabled;
|
||||
}
|
||||
set(collection, 'root.request.vars.res', vars);
|
||||
}
|
||||
},
|
||||
deleteCollectionVar: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const type = action.payload.type;
|
||||
if (collection) {
|
||||
if (type === 'request') {
|
||||
let vars = get(collection, 'root.request.vars.req', []);
|
||||
vars = filter(vars, (h) => h.uid !== action.payload.varUid);
|
||||
set(collection, 'root.request.vars.req', vars);
|
||||
} else if (type === 'response') {
|
||||
let vars = get(collection, 'root.request.vars.res', []);
|
||||
vars = filter(vars, (h) => h.uid !== action.payload.varUid);
|
||||
set(collection, 'root.request.vars.res', vars);
|
||||
}
|
||||
}
|
||||
},
|
||||
collectionAddFileEvent: (state, action) => {
|
||||
const file = action.payload.file;
|
||||
const isCollectionRoot = file.meta.collectionRoot ? true : false;
|
||||
@@ -1759,9 +1694,6 @@ export const {
|
||||
addCollectionHeader,
|
||||
updateCollectionHeader,
|
||||
deleteCollectionHeader,
|
||||
addCollectionVar,
|
||||
updateCollectionVar,
|
||||
deleteCollectionVar,
|
||||
updateCollectionAuthMode,
|
||||
updateCollectionAuth,
|
||||
updateCollectionRequestScript,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -787,25 +787,24 @@ export const getTotalRequestCountInCollection = (collection) => {
|
||||
};
|
||||
|
||||
export const getAllVariables = (collection, item) => {
|
||||
const envVariables = getEnvironmentVariables(collection);
|
||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
||||
const environmentVariables = getEnvironmentVariables(collection);
|
||||
let requestVariables = {};
|
||||
if (item?.request) {
|
||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||
requestVariables = mergeFolderLevelVars(item?.request, requestTreePath);
|
||||
}
|
||||
const pathParams = getPathParams(item);
|
||||
|
||||
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
||||
|
||||
return {
|
||||
...collectionVariables,
|
||||
...envVariables,
|
||||
...folderVariables,
|
||||
...environmentVariables,
|
||||
...requestVariables,
|
||||
...runtimeVariables,
|
||||
...collection.runtimeVariables,
|
||||
pathParams: {
|
||||
...pathParams
|
||||
},
|
||||
process: {
|
||||
env: {
|
||||
...processEnvVariables
|
||||
...collection.processEnvVariables
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -832,22 +831,14 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
const mergeVars = (collection, requestTreePath = []) => {
|
||||
let collectionVariables = {};
|
||||
let folderVariables = {};
|
||||
const mergeFolderLevelVars = (request, requestTreePath = []) => {
|
||||
let requestVariables = {};
|
||||
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
|
||||
collectionRequestVars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
collectionVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
for (let i of requestTreePath) {
|
||||
if (i.type === 'folder') {
|
||||
let vars = get(i, 'root.request.vars.req', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
folderVariables[_var.name] = _var.value;
|
||||
requestVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -859,9 +850,6 @@ const mergeVars = (collection, requestTreePath = []) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
collectionVariables,
|
||||
folderVariables,
|
||||
requestVariables
|
||||
};
|
||||
|
||||
return requestVariables;
|
||||
};
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -60,6 +60,20 @@ const runSingleRequest = async function (
|
||||
request.data = form;
|
||||
}
|
||||
|
||||
// run pre-request vars
|
||||
const preRequestVars = get(bruJson, 'request.vars.req');
|
||||
if (preRequestVars?.length) {
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||
varsRuntime.runPreRequestVars(
|
||||
preRequestVars,
|
||||
request,
|
||||
envVariables,
|
||||
runtimeVariables,
|
||||
collectionPath,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
// run pre request script
|
||||
const requestScriptFile = compact([
|
||||
get(collectionRoot, 'request.script.req'),
|
||||
@@ -262,7 +276,7 @@ const runSingleRequest = async function (
|
||||
|
||||
console.log(
|
||||
chalk.green(stripExtension(filename)) +
|
||||
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
|
||||
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
|
||||
);
|
||||
|
||||
// run post-response vars
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -290,10 +290,10 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
|
||||
// Filter out ZWNBSP character
|
||||
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
|
||||
data = data.replace(/^\uFEFF/, '');
|
||||
if (!disableParsingResponseJson) {
|
||||
if(!disableParsingResponseJson) {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
} catch { }
|
||||
} catch {}
|
||||
|
||||
return { data, dataBuffer };
|
||||
};
|
||||
@@ -319,6 +319,20 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) => {
|
||||
// run pre-request vars
|
||||
const preRequestVars = get(request, 'vars.req', []);
|
||||
if (preRequestVars?.length) {
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime });
|
||||
varsRuntime.runPreRequestVars(
|
||||
preRequestVars,
|
||||
request,
|
||||
envVars,
|
||||
runtimeVariables,
|
||||
collectionPath,
|
||||
processEnvVars
|
||||
);
|
||||
}
|
||||
|
||||
// run pre-request script
|
||||
let scriptResult;
|
||||
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL);
|
||||
@@ -1146,7 +1160,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
try {
|
||||
const disposition = contentDispositionParser.parse(contentDisposition);
|
||||
return disposition && disposition.parameters['filename'];
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const getFileNameFromUrlPath = () => {
|
||||
|
||||
@@ -12,17 +12,15 @@ const getContentType = (headers = {}) => {
|
||||
return contentType;
|
||||
};
|
||||
|
||||
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
|
||||
const collectionVariables = request?.collectionVariables || {};
|
||||
const folderVariables = request?.folderVariables || {};
|
||||
const interpolateVars = (request, envVars = {}, runtimeVariables = {}, processEnvVars = {}) => {
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
// we clone envVars because we don't want to modify the original object
|
||||
envVariables = cloneDeep(envVariables);
|
||||
envVars = cloneDeep(envVars);
|
||||
|
||||
// envVars can inturn have values as {{process.env.VAR_NAME}}
|
||||
// so we need to interpolate envVars first with processEnvVars
|
||||
forOwn(envVariables, (value, key) => {
|
||||
envVariables[key] = interpolate(value, {
|
||||
forOwn(envVars, (value, key) => {
|
||||
envVars[key] = interpolate(value, {
|
||||
process: {
|
||||
env: {
|
||||
...processEnvVars
|
||||
@@ -38,9 +36,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
|
||||
// runtimeVariables take precedence over envVars
|
||||
const combinedVars = {
|
||||
...collectionVariables,
|
||||
...envVariables,
|
||||
...folderVariables,
|
||||
...envVars,
|
||||
...requestVariables,
|
||||
...runtimeVariables,
|
||||
process: {
|
||||
|
||||
@@ -44,75 +44,73 @@ const mergeFolderLevelHeaders = (request, requestTreePath) => {
|
||||
request.headers = Array.from(requestHeadersMap, ([name, value]) => ({ name, value, enabled: true }));
|
||||
};
|
||||
|
||||
const mergeVars = (collection, request, requestTreePath) => {
|
||||
let reqVars = new Map();
|
||||
let collectionRequestVars = get(collection, 'root.request.vars.req', []);
|
||||
let collectionVariables = {};
|
||||
collectionRequestVars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
reqVars.set(_var.name, _var.value);
|
||||
collectionVariables[_var.name] = _var.value;
|
||||
}
|
||||
});
|
||||
let folderVariables = {};
|
||||
let requestVariables = {};
|
||||
const mergeFolderLevelVars = (request, requestTreePath) => {
|
||||
let folderReqVars = new Map();
|
||||
for (let i of requestTreePath) {
|
||||
if (i.type === 'folder') {
|
||||
let vars = get(i, 'root.request.vars.req', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
reqVars.set(_var.name, _var.value);
|
||||
folderVariables[_var.name] = _var.value;
|
||||
folderReqVars.set(_var.name, _var.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (i.uid === request.uid) {
|
||||
const vars = i?.draft ? get(i, 'draft.request.vars.req', []) : get(i, 'request.vars.req', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
reqVars.set(_var.name, _var.value);
|
||||
requestVariables[_var.name] = _var.value;
|
||||
folderReqVars.set(_var.name, _var.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
request.collectionVariables = collectionVariables;
|
||||
request.folderVariables = folderVariables;
|
||||
request.requestVariables = requestVariables;
|
||||
|
||||
request.vars.req = Array.from(reqVars, ([name, value]) => ({
|
||||
let mergedFolderReqVars = Array.from(folderReqVars, ([name, value]) => ({ name, value, enabled: true }));
|
||||
let requestReqVars = request?.vars?.req || [];
|
||||
let requestReqVarsMap = new Map();
|
||||
for (let _var of requestReqVars) {
|
||||
if (_var.enabled) {
|
||||
requestReqVarsMap.set(_var.name, _var.value);
|
||||
}
|
||||
}
|
||||
mergedFolderReqVars.forEach((_var) => {
|
||||
requestReqVarsMap.set(_var.name, _var.value);
|
||||
});
|
||||
request.vars.req = Array.from(requestReqVarsMap, ([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
enabled: true,
|
||||
type: 'request'
|
||||
}));
|
||||
|
||||
let resVars = new Map();
|
||||
let collectionResponseVars = get(collection, 'root.request.vars.res', []);
|
||||
collectionResponseVars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
resVars.set(_var.name, _var.value);
|
||||
}
|
||||
});
|
||||
let folderResVars = new Map();
|
||||
for (let i of requestTreePath) {
|
||||
if (i.type === 'folder') {
|
||||
let vars = get(i, 'root.request.vars.res', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
resVars.set(_var.name, _var.value);
|
||||
folderResVars.set(_var.name, _var.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (i.uid === request.uid) {
|
||||
const vars = i?.draft ? get(i, 'draft.request.vars.res', []) : get(i, 'request.vars.res', []);
|
||||
vars.forEach((_var) => {
|
||||
if (_var.enabled) {
|
||||
resVars.set(_var.name, _var.value);
|
||||
folderResVars.set(_var.name, _var.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
request.vars.res = Array.from(resVars, ([name, value]) => ({
|
||||
let mergedFolderResVars = Array.from(folderResVars, ([name, value]) => ({ name, value, enabled: true }));
|
||||
let requestResVars = request?.vars?.res || [];
|
||||
let requestResVarsMap = new Map();
|
||||
for (let _var of requestResVars) {
|
||||
if (_var.enabled) {
|
||||
requestResVarsMap.set(_var.name, _var.value);
|
||||
}
|
||||
}
|
||||
mergedFolderResVars.forEach((_var) => {
|
||||
requestResVarsMap.set(_var.name, _var.value);
|
||||
});
|
||||
request.vars.res = Array.from(requestResVarsMap, ([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
enabled: true,
|
||||
@@ -316,7 +314,7 @@ const prepareRequest = (item, collection) => {
|
||||
if (requestTreePath && requestTreePath.length > 0) {
|
||||
mergeFolderLevelHeaders(request, requestTreePath);
|
||||
mergeFolderLevelScripts(request, requestTreePath, scriptFlow);
|
||||
mergeVars(collection, request, requestTreePath);
|
||||
mergeFolderLevelVars(request, requestTreePath);
|
||||
}
|
||||
|
||||
each(request.headers, (h) => {
|
||||
@@ -403,9 +401,6 @@ const prepareRequest = (item, collection) => {
|
||||
}
|
||||
|
||||
axiosRequest.vars = request.vars;
|
||||
axiosRequest.collectionVariables = request.collectionVariables;
|
||||
axiosRequest.folderVariables = request.folderVariables;
|
||||
axiosRequest.requestVariables = request.requestVariables;
|
||||
axiosRequest.assertions = request.assertions;
|
||||
|
||||
return axiosRequest;
|
||||
|
||||
@@ -23,8 +23,7 @@ const defaultPreferences = {
|
||||
timeout: 0
|
||||
},
|
||||
font: {
|
||||
codeFont: 'default',
|
||||
codeFontSize: 14
|
||||
codeFont: 'default'
|
||||
},
|
||||
proxy: {
|
||||
enabled: false,
|
||||
@@ -55,8 +54,7 @@ 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({
|
||||
enabled: Yup.boolean(),
|
||||
|
||||
@@ -4,12 +4,10 @@ const { interpolate } = require('@usebruno/common');
|
||||
const variableNameRegex = /^[\w-.]*$/;
|
||||
|
||||
class Bru {
|
||||
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables) {
|
||||
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables) {
|
||||
this.envVariables = envVariables || {};
|
||||
this.runtimeVariables = runtimeVariables || {};
|
||||
this.processEnvVars = cloneDeep(processEnvVars || {});
|
||||
this.collectionVariables = collectionVariables || {};
|
||||
this.folderVariables = folderVariables || {};
|
||||
this.requestVariables = requestVariables || {};
|
||||
this.collectionPath = collectionPath;
|
||||
}
|
||||
@@ -20,9 +18,7 @@ class Bru {
|
||||
}
|
||||
|
||||
const combinedVars = {
|
||||
...this.collectionVariables,
|
||||
...this.envVariables,
|
||||
...this.folderVariables,
|
||||
...this.requestVariables,
|
||||
...this.runtimeVariables,
|
||||
process: {
|
||||
@@ -75,7 +71,7 @@ class Bru {
|
||||
if (variableNameRegex.test(key) === false) {
|
||||
throw new Error(
|
||||
`Variable name: "${key}" contains invalid characters!` +
|
||||
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
||||
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +82,7 @@ class Bru {
|
||||
if (variableNameRegex.test(key) === false) {
|
||||
throw new Error(
|
||||
`Variable name: "${key}" contains invalid characters!` +
|
||||
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
||||
' Names must only contain alpha-numeric characters, "-", "_", "."'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,14 +93,6 @@ class Bru {
|
||||
delete this.runtimeVariables[key];
|
||||
}
|
||||
|
||||
getCollectionVar(key) {
|
||||
return this._interpolate(this.collectionVariables[key]);
|
||||
}
|
||||
|
||||
getFolderVar(key) {
|
||||
return this._interpolate(this.folderVariables[key]);
|
||||
}
|
||||
|
||||
getRequestVar(key) {
|
||||
return this._interpolate(this.requestVariables[key]);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ const { interpolate } = require('@usebruno/common');
|
||||
|
||||
const interpolateString = (
|
||||
str,
|
||||
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {} }
|
||||
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, requestVariables = {} }
|
||||
) => {
|
||||
if (!str || !str.length || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
|
||||
const combinedVars = {
|
||||
...collectionVariables,
|
||||
...envVariables,
|
||||
...folderVariables,
|
||||
...requestVariables,
|
||||
...runtimeVariables,
|
||||
process: {
|
||||
|
||||
@@ -192,8 +192,6 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
|
||||
}
|
||||
|
||||
const interpolationContext = {
|
||||
collectionVariables: context.bru.collectionVariables,
|
||||
folderVariables: context.bru.folderVariables,
|
||||
requestVariables: context.bru.requestVariables,
|
||||
runtimeVariables: context.bru.runtimeVariables,
|
||||
envVariables: context.bru.envVariables,
|
||||
@@ -240,23 +238,13 @@ class AssertRuntime {
|
||||
}
|
||||
|
||||
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
||||
const collectionVariables = request?.collectionVariables || {};
|
||||
const folderVariables = request?.folderVariables || {};
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
||||
if (!enabledAssertions.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bru = new Bru(
|
||||
envVariables,
|
||||
runtimeVariables,
|
||||
processEnvVars,
|
||||
undefined,
|
||||
collectionVariables,
|
||||
folderVariables,
|
||||
requestVariables
|
||||
);
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, undefined, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
const res = createResponseParser(response);
|
||||
|
||||
@@ -267,9 +255,7 @@ class AssertRuntime {
|
||||
};
|
||||
|
||||
const context = {
|
||||
...collectionVariables,
|
||||
...envVariables,
|
||||
...folderVariables,
|
||||
...requestVariables,
|
||||
...runtimeVariables,
|
||||
...processEnvVars,
|
||||
|
||||
@@ -47,10 +47,8 @@ class ScriptRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
const collectionVariables = request?.collectionVariables || {};
|
||||
const folderVariables = request?.folderVariables || {};
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||
@@ -164,10 +162,8 @@ class ScriptRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
const collectionVariables = request?.collectionVariables || {};
|
||||
const folderVariables = request?.folderVariables || {};
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
const res = new BrunoResponse(response);
|
||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||
|
||||
@@ -48,10 +48,8 @@ class TestRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
const collectionVariables = request?.collectionVariables || {};
|
||||
const folderVariables = request?.folderVariables || {};
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
const res = new BrunoResponse(response);
|
||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
const _ = require('lodash');
|
||||
const Bru = require('../bru');
|
||||
const BrunoRequest = require('../bruno-request');
|
||||
const { evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
|
||||
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
||||
|
||||
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
script: literal,
|
||||
context,
|
||||
scriptType: 'template-literal'
|
||||
});
|
||||
}
|
||||
|
||||
return evaluateJsTemplateLiteral(literal, context);
|
||||
};
|
||||
|
||||
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
@@ -23,6 +35,35 @@ class VarsRuntime {
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
||||
if (!request?.requestVariables) {
|
||||
request.requestVariables = {};
|
||||
}
|
||||
const enabledVars = _.filter(vars, (v) => v.enabled);
|
||||
if (!enabledVars.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars);
|
||||
const req = new BrunoRequest(request);
|
||||
|
||||
const bruContext = {
|
||||
bru,
|
||||
req
|
||||
};
|
||||
|
||||
const context = {
|
||||
...envVariables,
|
||||
...runtimeVariables,
|
||||
...bruContext
|
||||
};
|
||||
|
||||
_.each(enabledVars, (v) => {
|
||||
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime);
|
||||
request?.requestVariables && (request.requestVariables[v.name] = value);
|
||||
});
|
||||
}
|
||||
|
||||
runPostResponseVars(vars, request, response, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const enabledVars = _.filter(vars, (v) => v.enabled);
|
||||
|
||||
@@ -69,18 +69,6 @@ const addBruShimToContext = (vm, bru) => {
|
||||
vm.setProp(bruObject, 'getRequestVar', getRequestVar);
|
||||
getRequestVar.dispose();
|
||||
|
||||
let getFolderVar = vm.newFunction('getFolderVar', function (key) {
|
||||
return marshallToVm(bru.getFolderVar(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getFolderVar', getFolderVar);
|
||||
getFolderVar.dispose();
|
||||
|
||||
let getCollectionVar = vm.newFunction('getCollectionVar', function (key) {
|
||||
return marshallToVm(bru.getCollectionVar(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
|
||||
getCollectionVar.dispose();
|
||||
|
||||
const sleep = vm.newFunction('sleep', (timer) => {
|
||||
const t = vm.getString(timer);
|
||||
const promise = vm.newPromise();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
headers {
|
||||
check: again
|
||||
token: {{collection_pre_var_token}}
|
||||
}
|
||||
|
||||
auth {
|
||||
@@ -11,11 +10,6 @@ auth:bearer {
|
||||
token: {{bearer_auth_token}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
collection_pre_var: collection_pre_var_value
|
||||
collection_pre_var_token: {{request_pre_var_token}}
|
||||
}
|
||||
|
||||
docs {
|
||||
# bruno-testbench 🐶
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,16 @@ body:json {
|
||||
}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
boolean: false
|
||||
undefined: undefined
|
||||
null: null
|
||||
string: foo
|
||||
number_1: 1
|
||||
number_2: 0
|
||||
number_3: -1
|
||||
}
|
||||
|
||||
assert {
|
||||
req.body.boolean: isBoolean false
|
||||
req.body.number_1: isNumber 1
|
||||
@@ -41,4 +51,35 @@ assert {
|
||||
req.body.number_3: eq -1
|
||||
req.body.number_2: isNumber
|
||||
req.body.number_3: isNumber
|
||||
boolean: eq false
|
||||
undefined: eq undefined
|
||||
null: eq null
|
||||
string: eq foo
|
||||
number_1: eq 1
|
||||
number_2: eq 0
|
||||
number_3: eq -1
|
||||
}
|
||||
|
||||
tests {
|
||||
test("boolean pre var", function() {
|
||||
expect(bru.getRequestVar('boolean')).to.eql(false);
|
||||
});
|
||||
|
||||
test("number pre var", function() {
|
||||
expect(bru.getRequestVar('number_1')).to.eql(1);
|
||||
expect(bru.getRequestVar('number_2')).to.eql(0);
|
||||
expect(bru.getRequestVar('number_3')).to.eql(-1);
|
||||
});
|
||||
|
||||
test("null pre var", function() {
|
||||
expect(bru.getRequestVar('null')).to.eql(null);
|
||||
});
|
||||
|
||||
test("undefined pre var", function() {
|
||||
expect(bru.getRequestVar('undefined')).to.eql(undefined);
|
||||
});
|
||||
|
||||
test("string pre var", function() {
|
||||
expect(bru.getRequestVar('string')).to.eql('foo');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
meta {
|
||||
name: string interpolation
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
folder_pre_var: folder_pre_var_value
|
||||
folder_pre_var_2: {{env.var1}}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
meta {
|
||||
name: pre-vars
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{host}}/ping
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
headers {
|
||||
request_pre_var: {{request_pre_var}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
request_pre_var: {{folder_pre_var}}
|
||||
request_pre_var_token: request_pre_var_token_value
|
||||
request_pre_var_1: request_pre_var_1_value
|
||||
request_pre_var_2: {{request_pre_var_1}}
|
||||
}
|
||||
|
||||
assert {
|
||||
collection_pre_var: eq collection_pre_var_value
|
||||
folder_pre_var: eq folder_pre_var_value
|
||||
}
|
||||
|
||||
tests {
|
||||
test("collection pre var bru function", function() {
|
||||
expect(bru.getCollectionVar('collection_pre_var')).to.eql('collection_pre_var_value');
|
||||
});
|
||||
|
||||
|
||||
test("folder pre var bru function", function() {
|
||||
expect(bru.getFolderVar('folder_pre_var')).to.eql('folder_pre_var_value');
|
||||
});
|
||||
|
||||
|
||||
test("request pre var bru function", function() {
|
||||
expect(bru.getRequestVar('request_pre_var')).to.eql('folder_pre_var_value');
|
||||
});
|
||||
|
||||
test("request pre var self-interpoaltion", function() {
|
||||
expect(bru.getRequestVar('request_pre_var_2')).to.eql('request_pre_var_1_value');
|
||||
});
|
||||
|
||||
}
|
||||
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