mirror of
https://github.com/usebruno/bruno.git
synced 2024-12-22 14:41:04 +01:00
Merge branch 'usebruno:main' into feature/save-prompt-tab-context-menu
This commit is contained in:
commit
d0460f0b94
@ -48,7 +48,7 @@ Bruno is being developed as a desktop app. You need to load the app by running t
|
|||||||
### Local Development
|
### Local Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# use nodejs 18 version
|
# use nodejs 20 version
|
||||||
nvm use
|
nvm use
|
||||||
|
|
||||||
# install deps
|
# install deps
|
||||||
|
12425
package-lock.json
generated
12425
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -51,10 +51,6 @@
|
|||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"rollup":"3.29.4"
|
"rollup":"3.29.5"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"json-bigint": "^1.0.0",
|
|
||||||
"lossless-json": "^4.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
|
output: 'export',
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
CI: process.env.CI,
|
CI: process.env.CI,
|
||||||
@ -10,6 +11,12 @@ module.exports = {
|
|||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
config.resolve.fallback.fs = false;
|
config.resolve.fallback.fs = false;
|
||||||
}
|
}
|
||||||
|
Object.defineProperty(config, 'devtool', {
|
||||||
|
get() {
|
||||||
|
return 'source-map';
|
||||||
|
},
|
||||||
|
set() {},
|
||||||
|
});
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENV=dev next dev -p 3000",
|
"dev": "cross-env ENV=dev next dev -p 3000",
|
||||||
"build": "next build && next export",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
@ -13,27 +13,25 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inter": "^5.0.15",
|
"@fontsource/inter": "^5.0.15",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@prantlf/jsonlint": "^16.0.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
|
||||||
"@reduxjs/toolkit": "^1.8.0",
|
"@reduxjs/toolkit": "^1.8.0",
|
||||||
"@tabler/icons": "^1.46.0",
|
"@tabler/icons": "^1.46.0",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
"@usebruno/graphql-docs": "0.1.0",
|
"@usebruno/graphql-docs": "0.1.0",
|
||||||
"@usebruno/schema": "0.7.0",
|
"@usebruno/schema": "0.7.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "1.7.5",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "5.65.2",
|
"codemirror": "5.65.2",
|
||||||
"codemirror-graphql": "1.2.5",
|
"codemirror-graphql": "2.1.1",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "0.7.1",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"file": "^0.2.2",
|
"file": "^0.2.2",
|
||||||
"file-dialog": "^0.0.8",
|
"file-dialog": "^0.0.8",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"github-markdown-css": "^5.2.0",
|
"github-markdown-css": "^5.2.0",
|
||||||
"graphiql": "^1.5.9",
|
"graphiql": "3.7.1",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"graphql-request": "^3.7.0",
|
"graphql-request": "^3.7.0",
|
||||||
"httpsnippet": "^3.0.6",
|
"httpsnippet": "^3.0.6",
|
||||||
@ -44,19 +42,18 @@
|
|||||||
"jshint": "^2.13.6",
|
"jshint": "^2.13.6",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonc-parser": "^3.2.1",
|
"jsonc-parser": "^3.2.1",
|
||||||
"jsonlint": "^1.6.3",
|
"jsonpath-plus": "10.1.0",
|
||||||
"jsonpath-plus": "^7.2.0",
|
|
||||||
"know-your-http-well": "^0.5.0",
|
"know-your-http-well": "^0.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^13.0.2",
|
"markdown-it": "^13.0.2",
|
||||||
"markdown-it-replace-link": "^1.2.0",
|
"markdown-it-replace-link": "^1.2.0",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "14.2.16",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "4.4.168",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "4.2.1",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"query-string": "^7.0.1",
|
"query-string": "^7.0.1",
|
||||||
@ -65,11 +62,10 @@
|
|||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-github-btn": "^1.4.0",
|
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-inspector": "^6.0.2",
|
"react-inspector": "^6.0.2",
|
||||||
"react-pdf": "^7.5.1",
|
"react-pdf": "9.1.1",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-tooltip": "^5.5.2",
|
"react-tooltip": "^5.5.2",
|
||||||
"sass": "^1.46.0",
|
"sass": "^1.46.0",
|
||||||
@ -87,15 +83,15 @@
|
|||||||
"@babel/preset-env": "^7.16.4",
|
"@babel/preset-env": "^7.16.4",
|
||||||
"@babel/preset-react": "^7.16.0",
|
"@babel/preset-react": "^7.16.0",
|
||||||
"@babel/runtime": "^7.16.3",
|
"@babel/runtime": "^7.16.3",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "10.4.20",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "^8.2.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.5.1",
|
"css-loader": "7.1.2",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-loader": "^3.0.1",
|
"html-loader": "^3.0.1",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"mini-css-extract-plugin": "^2.4.5",
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "8.4.47",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"webpack": "^5.64.4",
|
"webpack": "^5.64.4",
|
||||||
|
@ -10,7 +10,7 @@ import { isEqual, escapeRegExp } from 'lodash';
|
|||||||
import { getEnvironmentVariables } from 'utils/collections';
|
import { getEnvironmentVariables } from 'utils/collections';
|
||||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import jsonlint from 'jsonlint';
|
import * as jsonlint from '@prantlf/jsonlint';
|
||||||
import { JSHINT } from 'jshint';
|
import { JSHINT } from 'jshint';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
|
|
||||||
@ -73,7 +73,9 @@ if (!SERVER_RENDERED) {
|
|||||||
'bru.setNextRequest(requestName)',
|
'bru.setNextRequest(requestName)',
|
||||||
'req.disableParsingResponseJson()',
|
'req.disableParsingResponseJson()',
|
||||||
'bru.getRequestVar(key)',
|
'bru.getRequestVar(key)',
|
||||||
'bru.sleep(ms)'
|
'bru.sleep(ms)',
|
||||||
|
'bru.getGlobalEnvVar(key)',
|
||||||
|
'bru.setGlobalEnvVar(key, value)'
|
||||||
];
|
];
|
||||||
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
||||||
const cursor = editor.getCursor();
|
const cursor = editor.getCursor();
|
||||||
@ -249,17 +251,20 @@ export default class CodeEditor extends React.Component {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
let jsonlint = window.jsonlint.parser || window.jsonlint;
|
let jsonlint = window.jsonlint.parser || window.jsonlint;
|
||||||
jsonlint.parseError = function (str, hash) {
|
|
||||||
let loc = hash.loc;
|
|
||||||
found.push({
|
|
||||||
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
|
||||||
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
|
||||||
message: str
|
|
||||||
});
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
jsonlint.parse(stripJsonComments(text.replace(/(?<!"[^":{]*){{[^}]*}}(?![^"},]*")/g, '1')));
|
jsonlint.parse(stripJsonComments(text.replace(/(?<!"[^":{]*){{[^}]*}}(?![^"},]*")/g, '1')));
|
||||||
} catch (e) {}
|
} catch (error) {
|
||||||
|
const { message, location } = error;
|
||||||
|
const line = location?.start?.line;
|
||||||
|
const column = location?.start?.column;
|
||||||
|
if (line && column) {
|
||||||
|
found.push({
|
||||||
|
from: CodeMirror.Pos(line - 1, column),
|
||||||
|
to: CodeMirror.Pos(line - 1, column),
|
||||||
|
message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return found;
|
return found;
|
||||||
});
|
});
|
||||||
if (editor) {
|
if (editor) {
|
||||||
|
@ -36,6 +36,13 @@ const Wrapper = styled.div`
|
|||||||
padding: 0.35rem 0.6rem;
|
padding: 0.35rem 0.6rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
.icon {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: ${(props) => props.theme.dropdown.iconColor};
|
color: ${(props) => props.theme.dropdown.iconColor};
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||||||
import Tippy from '@tippyjs/react';
|
import Tippy from '@tippyjs/react';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const Dropdown = ({ icon, children, onCreate, placement }) => {
|
const Dropdown = ({ icon, children, onCreate, placement, transparent }) => {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="dropdown">
|
<StyledWrapper className="dropdown" transparent={transparent}>
|
||||||
<Tippy
|
<Tippy
|
||||||
content={children}
|
content={children}
|
||||||
placement={placement || 'bottom-end'}
|
placement={placement || 'bottom-end'}
|
||||||
|
@ -53,10 +53,11 @@ const EnvironmentSelector = ({ collection }) => {
|
|||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="flex items-center cursor-pointer environment-selector">
|
<div className="flex items-center cursor-pointer environment-selector">
|
||||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div className="label-item font-medium">Collection Environments</div>
|
||||||
{environments && environments.length
|
{environments && environments.length
|
||||||
? environments.map((e) => (
|
? environments.map((e) => (
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className={`dropdown-item ${e?.uid === activeEnvironmentUid ? 'active' : ''}`}
|
||||||
key={e.uid}
|
key={e.uid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect(e);
|
onSelect(e);
|
||||||
|
@ -39,6 +39,11 @@ const Wrapper = styled.div`
|
|||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip-mod {
|
||||||
|
font-size: 11px !important;
|
||||||
|
width: 150px !important;
|
||||||
|
}
|
||||||
|
|
||||||
input[type='text'] {
|
input[type='text'] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: solid 1px transparent;
|
border: solid 1px transparent;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useRef, useEffect } from 'react';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { IconTrash } from '@tabler/icons';
|
import { IconTrash, IconAlertCircle } from '@tabler/icons';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
@ -11,6 +11,7 @@ import * as Yup from 'yup';
|
|||||||
import { variableNameRegex } from 'utils/common/regex';
|
import { variableNameRegex } from 'utils/common/regex';
|
||||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
|
||||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
|
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -59,14 +60,15 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
|
|
||||||
const ErrorMessage = ({ name }) => {
|
const ErrorMessage = ({ name }) => {
|
||||||
const meta = formik.getFieldMeta(name);
|
const meta = formik.getFieldMeta(name);
|
||||||
if (!meta.error) {
|
const id = uuid();
|
||||||
|
if (!meta.error || !meta.touched) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label htmlFor={name} className="text-red-500">
|
<span>
|
||||||
{meta.error}
|
<IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
|
||||||
</label>
|
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,6 +126,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -137,6 +140,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
/>
|
/>
|
||||||
<ErrorMessage name={`${index}.name`} />
|
<ErrorMessage name={`${index}.name`} />
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="flex flex-row flex-nowrap">
|
<td className="flex flex-row flex-nowrap">
|
||||||
<div className="overflow-hidden grow w-full relative">
|
<div className="overflow-hidden grow w-full relative">
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
.current-environment {
|
||||||
|
}
|
||||||
|
.environment-active {
|
||||||
|
padding: 0.3rem 0.4rem;
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
|
||||||
|
}
|
||||||
|
.environment-selector {
|
||||||
|
.active: {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,97 @@
|
|||||||
|
import React, { useRef, forwardRef, useState } from 'react';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { IconSettings, IconWorld, IconDatabase, IconDatabaseOff, IconCheck } from '@tabler/icons';
|
||||||
|
import EnvironmentSettings from '../EnvironmentSettings';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
import ToolHint from 'components/ToolHint/index';
|
||||||
|
|
||||||
|
const EnvironmentSelector = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
|
||||||
|
const activeGlobalEnvironmentUid = useSelector((state) => state.globalEnvironments.activeGlobalEnvironmentUid);
|
||||||
|
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||||
|
const activeEnvironment = activeGlobalEnvironmentUid ? find(globalEnvironments, (e) => e.uid === activeGlobalEnvironmentUid) : null;
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
|
||||||
|
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
|
||||||
|
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||||
|
{
|
||||||
|
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
|
||||||
|
}
|
||||||
|
</ToolHint>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSettingsIconClick = () => {
|
||||||
|
setOpenSettingsModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setOpenSettingsModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
|
||||||
|
const onSelect = (environment) => {
|
||||||
|
dispatch(selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null }))
|
||||||
|
.then(() => {
|
||||||
|
if (environment) {
|
||||||
|
toast.success(`Environment changed to ${environment.name}`);
|
||||||
|
} else {
|
||||||
|
toast.success(`No Environments are active now`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="flex items-center cursor-pointer environment-selector mr-3">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end" transparent={true}>
|
||||||
|
<div className="label-item font-medium">Global Environments</div>
|
||||||
|
{globalEnvironments && globalEnvironments.length
|
||||||
|
? globalEnvironments.map((e) => (
|
||||||
|
<div
|
||||||
|
className={`dropdown-item ${e?.uid === activeGlobalEnvironmentUid ? 'active' : ''}`}
|
||||||
|
key={e.uid}
|
||||||
|
onClick={() => {
|
||||||
|
onSelect(e);
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onSelect(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||||
|
<span className="ml-2">No Environment</span>
|
||||||
|
</div>
|
||||||
|
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
||||||
|
<div className="pr-2 text-gray-600">
|
||||||
|
<IconSettings size={18} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<span>Configure</span>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
{openSettingsModal && <EnvironmentSettings globalEnvironments={globalEnvironments} activeGlobalEnvironmentUid={activeGlobalEnvironmentUid} onClose={handleModalClose} />}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentSelector;
|
@ -0,0 +1,78 @@
|
|||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { copyGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const CopyEnvironment = ({ environment, onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: environment.name + ' - Copy'
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
dispatch(copyGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global environment created!');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error('An error occurred while created the environment');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal size="sm" title={'Copy Global Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
New Environment Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox mt-2 w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyEnvironment;
|
@ -0,0 +1,83 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
|
||||||
|
const CreateEnvironment = ({ onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
dispatch(addGlobalEnvironment({ name: values.name }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global environment created!');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch(() => toast.error('An error occurred while creating the environment'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Create Global Environment'}
|
||||||
|
confirmText="Create"
|
||||||
|
handleConfirm={onSubmit}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
Environment Name
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center mt-2">
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateEnvironment;
|
@ -0,0 +1,15 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
button.submit {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--color-background-danger) !important;
|
||||||
|
border: inherit !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: inherit !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { deleteGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
|
||||||
|
const DeleteEnvironment = ({ onClose, environment }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onConfirm = () => {
|
||||||
|
dispatch(deleteGlobalEnvironment({ environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global Environment deleted successfully');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch(() => toast.error('An error occurred while deleting the environment'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Delete Global Environment'}
|
||||||
|
confirmText="Delete"
|
||||||
|
handleConfirm={onConfirm}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteEnvironment;
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IconAlertTriangle } from '@tabler/icons';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const ConfirmSwitchEnv = ({ onCancel }) => {
|
||||||
|
return createPortal(
|
||||||
|
<Modal
|
||||||
|
size="md"
|
||||||
|
title="Unsaved changes"
|
||||||
|
confirmText="Save and Close"
|
||||||
|
cancelText="Close without saving"
|
||||||
|
disableEscapeKey={true}
|
||||||
|
disableCloseOnOutsideClick={true}
|
||||||
|
closeModalFadeTimeout={150}
|
||||||
|
handleCancel={onCancel}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
hideFooter={true}
|
||||||
|
>
|
||||||
|
<div className="flex items-center font-normal">
|
||||||
|
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
|
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
||||||
|
</div>
|
||||||
|
<div className="font-normal mt-4">You have unsaved changes in this environment.</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<div>
|
||||||
|
<button className="btn btn-sm btn-danger" onClick={onCancel}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</Modal>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmSwitchEnv;
|
@ -0,0 +1,66 @@
|
|||||||
|
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.collection.environment.settings.gridBorder};
|
||||||
|
padding: 4px 10px;
|
||||||
|
|
||||||
|
&:nth-child(1),
|
||||||
|
&:nth-child(4) {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
&:nth-child(5) {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
color: ${(props) => props.theme.table.thead.color};
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
thead td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-param {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-mod {
|
||||||
|
font-size: 11px !important;
|
||||||
|
width: 150px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
outline: none !important;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,199 @@
|
|||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import { IconTrash, IconAlertCircle } from '@tabler/icons';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { uuid } from 'utils/common';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { variableNameRegex } from 'utils/common/regex';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
|
||||||
|
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const addButtonRef = useRef(null);
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: environment.variables || [],
|
||||||
|
validationSchema: Yup.array().of(
|
||||||
|
Yup.object({
|
||||||
|
enabled: Yup.boolean(),
|
||||||
|
name: Yup.string()
|
||||||
|
.required('Name cannot be empty')
|
||||||
|
.matches(
|
||||||
|
variableNameRegex,
|
||||||
|
'Name contains invalid characters. Must only contain alphanumeric characters, "-", "_", "." and cannot start with a digit.'
|
||||||
|
)
|
||||||
|
.trim(),
|
||||||
|
secret: Yup.boolean(),
|
||||||
|
type: Yup.string(),
|
||||||
|
uid: Yup.string(),
|
||||||
|
value: Yup.string().trim().nullable()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (!formik.dirty) {
|
||||||
|
toast.error('Nothing to save');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(saveGlobalEnvironment({ environmentUid: environment.uid, variables: cloneDeep(values) }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Changes saved successfully');
|
||||||
|
formik.resetForm({ values });
|
||||||
|
setIsModified(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('An error occurred while saving the changes')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Effect to track modifications.
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsModified(formik.dirty);
|
||||||
|
}, [formik.dirty]);
|
||||||
|
|
||||||
|
const ErrorMessage = ({ name }) => {
|
||||||
|
const meta = formik.getFieldMeta(name);
|
||||||
|
const id = uuid();
|
||||||
|
if (!meta.error || !meta.touched) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
|
||||||
|
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addVariable = () => {
|
||||||
|
const newVariable = {
|
||||||
|
uid: uuid(),
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
type: 'text',
|
||||||
|
secret: false,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveVar = (id) => {
|
||||||
|
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formik.dirty) {
|
||||||
|
// Smooth scrolling to the changed parameter is temporarily disabled
|
||||||
|
// due to UX issues when editing the first row in a long list of environment variables.
|
||||||
|
// addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, [formik.values, formik.dirty]);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
formik.resetForm({ originalEnvironmentVariables });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full mt-6 mb-6">
|
||||||
|
<div className="h-[50vh] overflow-y-auto w-full">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="text-center">Enabled</td>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>Value</td>
|
||||||
|
<td className="text-center">Secret</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{formik.values.map((variable, index) => (
|
||||||
|
<tr key={variable.uid}>
|
||||||
|
<td className="text-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mousetrap"
|
||||||
|
name={`${index}.enabled`}
|
||||||
|
checked={variable.enabled}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
className="mousetrap"
|
||||||
|
id={`${index}.name`}
|
||||||
|
name={`${index}.name`}
|
||||||
|
value={variable.name}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
<ErrorMessage name={`${index}.name`} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="flex flex-row flex-nowrap">
|
||||||
|
<div className="overflow-hidden grow w-full relative">
|
||||||
|
<SingleLineEditor
|
||||||
|
theme={storedTheme}
|
||||||
|
name={`${index}.value`}
|
||||||
|
value={variable.value}
|
||||||
|
isSecret={variable.secret}
|
||||||
|
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="mousetrap"
|
||||||
|
name={`${index}.secret`}
|
||||||
|
checked={variable.secret}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button onClick={() => handleRemoveVar(variable.uid)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
ref={addButtonRef}
|
||||||
|
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||||
|
onClick={addVariable}
|
||||||
|
>
|
||||||
|
+ Add Variable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default EnvironmentVariables;
|
@ -0,0 +1,46 @@
|
|||||||
|
import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import CopyEnvironment from '../../CopyEnvironment';
|
||||||
|
import DeleteEnvironment from '../../DeleteEnvironment';
|
||||||
|
import RenameEnvironment from '../../RenameEnvironment';
|
||||||
|
import EnvironmentVariables from './EnvironmentVariables';
|
||||||
|
|
||||||
|
const EnvironmentDetails = ({ environment, setIsModified }) => {
|
||||||
|
const [openEditModal, setOpenEditModal] = useState(false);
|
||||||
|
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||||
|
const [openCopyModal, setOpenCopyModal] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
|
||||||
|
{openEditModal && (
|
||||||
|
<RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} />
|
||||||
|
)}
|
||||||
|
{openDeleteModal && (
|
||||||
|
<DeleteEnvironment
|
||||||
|
onClose={() => setOpenDeleteModal(false)}
|
||||||
|
environment={environment}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{openCopyModal && (
|
||||||
|
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} />
|
||||||
|
)}
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex flex-grow items-center">
|
||||||
|
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||||
|
<span className="ml-1 font-semibold break-all">{environment.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-x-4 pl-4">
|
||||||
|
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
|
||||||
|
<IconCopy className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenCopyModal(true)} />
|
||||||
|
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<EnvironmentVariables environment={environment} setIsModified={setIsModified} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentDetails;
|
@ -0,0 +1,58 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
margin-inline: -1rem;
|
||||||
|
margin-block: -1.5rem;
|
||||||
|
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.bg};
|
||||||
|
|
||||||
|
.environments-sidebar {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||||
|
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||||
|
min-height: 400px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-item {
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-left: solid 2px transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
|
||||||
|
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
|
||||||
|
&:hover {
|
||||||
|
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create-environment,
|
||||||
|
.btn-import-environment {
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: none;
|
||||||
|
color: ${(props) => props.theme.textLink};
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-import-environment {
|
||||||
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,149 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import usePrevious from 'hooks/usePrevious';
|
||||||
|
import EnvironmentDetails from './EnvironmentDetails';
|
||||||
|
import CreateEnvironment from '../CreateEnvironment';
|
||||||
|
import { IconDownload, IconShieldLock } from '@tabler/icons';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||||
|
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
|
||||||
|
import ImportEnvironment from '../ImportEnvironment';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
|
||||||
|
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||||
|
const [openImportModal, setOpenImportModal] = useState(false);
|
||||||
|
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
|
||||||
|
|
||||||
|
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
|
||||||
|
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
|
||||||
|
|
||||||
|
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||||
|
const prevEnvUids = usePrevious(envUids);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!environments?.length) {
|
||||||
|
setSelectedEnvironment(null);
|
||||||
|
setOriginalEnvironmentVariables([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEnvironment) {
|
||||||
|
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
|
||||||
|
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
|
||||||
|
if (hasSelectedEnvironmentChanged) {
|
||||||
|
setSelectedEnvironment(_selectedEnvironment);
|
||||||
|
}
|
||||||
|
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const environment = environments?.find(env => env.uid === activeEnvironmentUid) || environments?.[0];
|
||||||
|
|
||||||
|
setSelectedEnvironment(environment);
|
||||||
|
setOriginalEnvironmentVariables(environment?.variables || []);
|
||||||
|
}, [environments, activeEnvironmentUid]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||||
|
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||||
|
if (newEnv) {
|
||||||
|
setSelectedEnvironment(newEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||||
|
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||||
|
}
|
||||||
|
}, [envUids, environments, prevEnvUids]);
|
||||||
|
|
||||||
|
const handleEnvironmentClick = (env) => {
|
||||||
|
if (!isModified) {
|
||||||
|
setSelectedEnvironment(env);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedEnvironment) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateEnvClick = () => {
|
||||||
|
if (!isModified) {
|
||||||
|
setOpenCreateModal(true);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportClick = () => {
|
||||||
|
if (!isModified) {
|
||||||
|
setOpenImportModal(true);
|
||||||
|
} else {
|
||||||
|
setSwitchEnvConfirmClose(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSecretsClick = () => {
|
||||||
|
setOpenManageSecretsModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSwitch = (saveChanges) => {
|
||||||
|
if (!saveChanges) {
|
||||||
|
setSwitchEnvConfirmClose(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
{openCreateModal && <CreateEnvironment onClose={() => setOpenCreateModal(false)} />}
|
||||||
|
{openImportModal && <ImportEnvironment onClose={() => setOpenImportModal(false)} />}
|
||||||
|
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
|
||||||
|
|
||||||
|
<div className="flex">
|
||||||
|
<div>
|
||||||
|
{switchEnvConfirmClose && (
|
||||||
|
<div className="flex items-center justify-between tab-container px-1">
|
||||||
|
<ConfirmSwitchEnv onCancel={() => handleConfirmSwitch(false)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="environments-sidebar flex flex-col">
|
||||||
|
{environments &&
|
||||||
|
environments.length &&
|
||||||
|
environments.map((env) => (
|
||||||
|
<div
|
||||||
|
key={env.uid}
|
||||||
|
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||||
|
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||||
|
>
|
||||||
|
<span className="break-all">{env.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||||
|
+ <span>Create</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-auto btn-import-environment">
|
||||||
|
<div className="flex items-center" onClick={() => handleImportClick()}>
|
||||||
|
<IconDownload size={12} strokeWidth={2} />
|
||||||
|
<span className="label ml-1 text-xs">Import</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mt-2" onClick={() => handleSecretsClick()}>
|
||||||
|
<IconShieldLock size={12} strokeWidth={2} />
|
||||||
|
<span className="label ml-1 text-xs">Managing Secrets</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EnvironmentDetails
|
||||||
|
environment={selectedEnvironment}
|
||||||
|
setIsModified={setIsModified}
|
||||||
|
originalEnvironmentVariables={originalEnvironmentVariables}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentList;
|
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Portal from 'components/Portal';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import importPostmanEnvironment from 'utils/importers/postman-environment';
|
||||||
|
import { toastError } from 'utils/common/error';
|
||||||
|
import { IconDatabaseImport } from '@tabler/icons';
|
||||||
|
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
import { uuid } from 'utils/common/index';
|
||||||
|
|
||||||
|
const ImportEnvironment = ({ onClose }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleImportPostmanEnvironment = () => {
|
||||||
|
importPostmanEnvironment()
|
||||||
|
.then((environments) => {
|
||||||
|
environments
|
||||||
|
.filter((env) =>
|
||||||
|
env.name && env.name !== 'undefined'
|
||||||
|
? true
|
||||||
|
: () => {
|
||||||
|
toast.error('Failed to import environment: env has no name');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map((environment) => {
|
||||||
|
let variables = environment?.variables?.map(v => ({
|
||||||
|
...v,
|
||||||
|
uid: uuid(),
|
||||||
|
type: 'text'
|
||||||
|
}));
|
||||||
|
dispatch(addGlobalEnvironment({ name: environment.name, variables }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Global Environment imported successfully');
|
||||||
|
})
|
||||||
|
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((err) => toastError(err, 'Postman Import environment failed'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal size="sm" title="Import Global Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleImportPostmanEnvironment}
|
||||||
|
className="flex justify-center flex-col items-center w-full dark:bg-zinc-700 rounded-lg border-2 border-dashed border-zinc-300 dark:border-zinc-400 p-12 text-center hover:border-zinc-400 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<IconDatabaseImport size={64} />
|
||||||
|
<span className="mt-2 block text-sm font-semibold">Import your Postman environments</span>
|
||||||
|
</button>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImportEnvironment;
|
@ -0,0 +1,88 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Portal from 'components/Portal/index';
|
||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
|
||||||
|
const RenameEnvironment = ({ onClose, environment }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const formik = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
name: environment.name
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.max(50, 'must be 50 characters or less')
|
||||||
|
.required('name is required')
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (values.name === environment.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(renameGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Environment renamed successfully');
|
||||||
|
onClose();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error('An error occurred while renaming the environment');
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={'Rename Environment'}
|
||||||
|
confirmText="Rename"
|
||||||
|
handleConfirm={onSubmit}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block font-semibold">
|
||||||
|
Environment Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="environment-name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
ref={inputRef}
|
||||||
|
className="block textbox mt-2 w-full"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.name || ''}
|
||||||
|
/>
|
||||||
|
{formik.touched.name && formik.errors.name ? (
|
||||||
|
<div className="text-red-500">{formik.errors.name}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenameEnvironment;
|
@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
button.btn-create-environment {
|
||||||
|
&:hover {
|
||||||
|
span {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,78 @@
|
|||||||
|
import Modal from 'components/Modal/index';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import CreateEnvironment from './CreateEnvironment';
|
||||||
|
import EnvironmentList from './EnvironmentList';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { IconFileAlert } from '@tabler/icons';
|
||||||
|
import ImportEnvironment from './ImportEnvironment/index';
|
||||||
|
|
||||||
|
export const SharedButton = ({ children, className, onClick }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||||
|
${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultTab = ({ setTab }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center items-center flex flex-col">
|
||||||
|
<IconFileAlert size={64} strokeWidth={1} />
|
||||||
|
<span className="font-semibold mt-2">No Global Environments found</span>
|
||||||
|
<div className="flex items-center justify-center mt-6">
|
||||||
|
<SharedButton onClick={() => setTab('create')}>
|
||||||
|
<span>Create Global Environment</span>
|
||||||
|
</SharedButton>
|
||||||
|
|
||||||
|
<span className="mx-4">Or</span>
|
||||||
|
|
||||||
|
<SharedButton onClick={() => setTab('import')}>
|
||||||
|
<span>Import Environment</span>
|
||||||
|
</SharedButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, onClose }) => {
|
||||||
|
const [isModified, setIsModified] = useState(false);
|
||||||
|
const environments = globalEnvironments;
|
||||||
|
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||||
|
const [tab, setTab] = useState('default');
|
||||||
|
if (!environments || !environments.length) {
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal size="md" title="Global Environments" handleCancel={onClose} hideCancel={true} hideFooter={true}>
|
||||||
|
{tab === 'create' ? (
|
||||||
|
<CreateEnvironment onClose={() => setTab('default')} />
|
||||||
|
) : tab === 'import' ? (
|
||||||
|
<ImportEnvironment onClose={() => setTab('default')} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<DefaultTab setTab={setTab} />
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal size="lg" title="Global Environments" handleCancel={onClose} hideFooter={true}>
|
||||||
|
<EnvironmentList
|
||||||
|
environments={globalEnvironments}
|
||||||
|
activeEnvironmentUid={activeGlobalEnvironmentUid}
|
||||||
|
selectedEnvironment={selectedEnvironment}
|
||||||
|
setSelectedEnvironment={setSelectedEnvironment}
|
||||||
|
isModified={isModified}
|
||||||
|
setIsModified={setIsModified}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentSettings;
|
@ -1,19 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
|
||||||
.collection-dropdown {
|
|
||||||
color: rgb(110 110 110);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tippy-box {
|
|
||||||
top: -0.5rem;
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default StyledWrapper;
|
|
@ -1,60 +0,0 @@
|
|||||||
import React, { useState, forwardRef, useRef } from 'react';
|
|
||||||
import Dropdown from '../Dropdown';
|
|
||||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { IconBox, IconSearch, IconDots } from '@tabler/icons';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
|
||||||
|
|
||||||
const Navbar = () => {
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
|
||||||
|
|
||||||
const menuDropdownTippyRef = useRef();
|
|
||||||
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
|
||||||
const MenuIcon = forwardRef((props, ref) => {
|
|
||||||
return (
|
|
||||||
<div ref={ref} className="dropdown-icon cursor-pointer">
|
|
||||||
<IconDots size={22} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper className="px-2 py-2 flex items-center">
|
|
||||||
<div>
|
|
||||||
<span className="ml-2">Collections</span>
|
|
||||||
{/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */}
|
|
||||||
</div>
|
|
||||||
<div className="collection-dropdown flex flex-grow items-center justify-end">
|
|
||||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
|
||||||
<div
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
menuDropdownTippyRef.current.hide();
|
|
||||||
setModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create Collection
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
menuDropdownTippyRef.current.hide();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import Collection
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
menuDropdownTippyRef.current.hide();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</div>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Navbar;
|
|
@ -9,7 +9,6 @@ import StyledWrapper from './StyledWrapper';
|
|||||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
|
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
|
||||||
import { toastError } from 'utils/common/error';
|
import { toastError } from 'utils/common/error';
|
||||||
import { format, applyEdits } from 'jsonc-parser';
|
import { format, applyEdits } from 'jsonc-parser';
|
||||||
import { parse, stringify } from 'lossless-json';
|
|
||||||
import xmlFormat from 'xml-formatter';
|
import xmlFormat from 'xml-formatter';
|
||||||
|
|
||||||
const RequestBodyMode = ({ item, collection }) => {
|
const RequestBodyMode = ({ item, collection }) => {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
.folder-list {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
.folder-name {
|
|
||||||
padding-block: 8px;
|
|
||||||
padding-inline: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
@ -1,55 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { faFolder } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
|
||||||
import Modal from 'components//Modal';
|
|
||||||
|
|
||||||
const SaveRequest = ({ items, onClose }) => {
|
|
||||||
const [showFolders, setShowFolders] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setShowFolders(items || []);
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
const handleFolderClick = (folder) => {
|
|
||||||
let subFolders = [];
|
|
||||||
if (folder.items && folder.items.length) {
|
|
||||||
for (let item of folder.items) {
|
|
||||||
if (item.items) {
|
|
||||||
subFolders.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subFolders.length) {
|
|
||||||
setShowFolders(subFolders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper>
|
|
||||||
<Modal
|
|
||||||
size="md"
|
|
||||||
title="Save Request"
|
|
||||||
confirmText="Save"
|
|
||||||
cancelText="Cancel"
|
|
||||||
handleCancel={onClose}
|
|
||||||
handleConfirm={onClose}
|
|
||||||
>
|
|
||||||
<p className="mb-2">Select a folder to save request:</p>
|
|
||||||
<div className="folder-list">
|
|
||||||
{showFolders && showFolders.length
|
|
||||||
? showFolders.map((folder) => (
|
|
||||||
<div key={folder.uid} className="folder-name" onClick={() => handleFolderClick(folder)}>
|
|
||||||
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{ fontSize: 20 }} />
|
|
||||||
{folder.name}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SaveRequest;
|
|
@ -20,6 +20,8 @@ import { DocExplorer } from '@usebruno/graphql-docs';
|
|||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import SecuritySettings from 'components/SecuritySettings';
|
import SecuritySettings from 'components/SecuritySettings';
|
||||||
import FolderSettings from 'components/FolderSettings';
|
import FolderSettings from 'components/FolderSettings';
|
||||||
|
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
|
||||||
const MIN_LEFT_PANE_WIDTH = 300;
|
const MIN_LEFT_PANE_WIDTH = 300;
|
||||||
const MIN_RIGHT_PANE_WIDTH = 350;
|
const MIN_RIGHT_PANE_WIDTH = 350;
|
||||||
@ -32,11 +34,25 @@ const RequestTabPanel = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const tabs = useSelector((state) => state.tabs.tabs);
|
const tabs = useSelector((state) => state.tabs.tabs);
|
||||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||||
const collections = useSelector((state) => state.collections.collections);
|
|
||||||
const screenWidth = useSelector((state) => state.app.screenWidth);
|
|
||||||
|
|
||||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
|
||||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||||
|
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||||
|
const _collections = useSelector((state) => state.collections.collections);
|
||||||
|
|
||||||
|
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
|
||||||
|
let collections = produce(_collections, draft => {
|
||||||
|
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
// add selected global env variables to the collection object
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let collection = find(collections, (c) => c.uid === focusedTab?.collectionUid);
|
||||||
|
|
||||||
|
const screenWidth = useSelector((state) => state.app.screenWidth);
|
||||||
|
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||||
const [leftPaneWidth, setLeftPaneWidth] = useState(
|
const [leftPaneWidth, setLeftPaneWidth] = useState(
|
||||||
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
|
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
|
||||||
); // 2.2 so that request pane is relatively smaller
|
); // 2.2 so that request pane is relatively smaller
|
||||||
@ -117,7 +133,6 @@ const RequestTabPanel = () => {
|
|||||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
|
|
||||||
if (!collection || !collection.uid) {
|
if (!collection || !collection.uid) {
|
||||||
return <div className="pb-4 px-4">Collection not found!</div>;
|
return <div className="pb-4 px-4">Collection not found!</div>;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||||
|
import GlobalEnvironmentSelector from 'components/GlobalEnvironments/EnvironmentSelector';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import ToolHint from 'components/ToolHint';
|
import ToolHint from 'components/ToolHint';
|
||||||
@ -48,7 +49,7 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<IconFiles size={18} strokeWidth={1.5} />
|
<IconFiles size={18} strokeWidth={1.5} />
|
||||||
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 items-center justify-end">
|
<div className="flex flex-3 items-center justify-end">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
<JsSandboxMode collection={collection} />
|
<JsSandboxMode collection={collection} />
|
||||||
</span>
|
</span>
|
||||||
@ -67,6 +68,9 @@ const CollectionToolBar = ({ collection }) => {
|
|||||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||||
</ToolHint>
|
</ToolHint>
|
||||||
</span>
|
</span>
|
||||||
|
<span>
|
||||||
|
<GlobalEnvironmentSelector />
|
||||||
|
</span>
|
||||||
<EnvironmentSelector collection={collection} />
|
<EnvironmentSelector collection={collection} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,6 +7,8 @@ import { useState } from 'react';
|
|||||||
import 'pdfjs-dist/build/pdf.worker';
|
import 'pdfjs-dist/build/pdf.worker';
|
||||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||||
|
import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
|
||||||
|
GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';
|
||||||
|
|
||||||
const QueryResultPreview = ({
|
const QueryResultPreview = ({
|
||||||
previewTab,
|
previewTab,
|
||||||
|
@ -8,19 +8,27 @@ import { useSelector } from 'react-redux';
|
|||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { IconCopy } from '@tabler/icons';
|
import { IconCopy } from '@tabler/icons';
|
||||||
import { findCollectionByItemUid } from '../../../../../../../utils/collections/index';
|
import { findCollectionByItemUid, getGlobalEnvironmentVariables } from '../../../../../../../utils/collections/index';
|
||||||
import { getAuthHeaders } from '../../../../../../../utils/codegenerator/auth';
|
import { getAuthHeaders } from '../../../../../../../utils/codegenerator/auth';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
const CodeView = ({ language, item }) => {
|
const CodeView = ({ language, item }) => {
|
||||||
const { displayedTheme } = useTheme();
|
const { displayedTheme } = useTheme();
|
||||||
const preferences = useSelector((state) => state.app.preferences);
|
const preferences = useSelector((state) => state.app.preferences);
|
||||||
|
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||||
const { target, client, language: lang } = language;
|
const { target, client, language: lang } = language;
|
||||||
const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
|
const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
|
||||||
const collection = findCollectionByItemUid(
|
let _collection = findCollectionByItemUid(
|
||||||
useSelector((state) => state.collections.collections),
|
useSelector((state) => state.collections.collections),
|
||||||
item.uid
|
item.uid
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let collection = cloneDeep(_collection);
|
||||||
|
|
||||||
|
// add selected global env variables to the collection object
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
|
||||||
const collectionRootAuth = collection?.root?.request?.auth;
|
const collectionRootAuth = collection?.root?.request?.auth;
|
||||||
const requestAuth = item.draft ? get(item, 'draft.request.auth') : get(item, 'request.auth');
|
const requestAuth = item.draft ? get(item, 'draft.request.auth') : get(item, 'request.auth');
|
||||||
|
|
||||||
|
@ -3,15 +3,20 @@ import { useState } from 'react';
|
|||||||
import CodeView from './CodeView';
|
import CodeView from './CodeView';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { isValidUrl } from 'utils/url';
|
import { isValidUrl } from 'utils/url';
|
||||||
import { find, get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { findEnvironmentInCollection } from 'utils/collections';
|
import { findEnvironmentInCollection } from 'utils/collections';
|
||||||
import { interpolateUrl, interpolateUrlPathParams } from 'utils/url/index';
|
import { interpolateUrl, interpolateUrlPathParams } from 'utils/url/index';
|
||||||
import { getLanguages } from 'utils/codegenerator/targets';
|
import { getLanguages } from 'utils/codegenerator/targets';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
||||||
|
|
||||||
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
||||||
const languages = getLanguages();
|
const languages = getLanguages();
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
|
||||||
|
const environment = findEnvironmentInCollection(collection, collection?.activeEnvironmentUid);
|
||||||
let envVars = {};
|
let envVars = {};
|
||||||
if (environment) {
|
if (environment) {
|
||||||
const vars = get(environment, 'variables', []);
|
const vars = get(environment, 'variables', []);
|
||||||
@ -27,6 +32,7 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
// interpolate the url
|
// interpolate the url
|
||||||
const interpolatedUrl = interpolateUrl({
|
const interpolatedUrl = interpolateUrl({
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
|
globalEnvironmentVariables,
|
||||||
envVars,
|
envVars,
|
||||||
runtimeVariables: collection.runtimeVariables,
|
runtimeVariables: collection.runtimeVariables,
|
||||||
processEnvVars: collection.processEnvVariables
|
processEnvVars: collection.processEnvVariables
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import TitleBar from './TitleBar';
|
import TitleBar from './TitleBar';
|
||||||
import Collections from './Collections';
|
import Collections from './Collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import GitHubButton from 'react-github-btn';
|
|
||||||
import Preferences from 'components/Preferences';
|
import Preferences from 'components/Preferences';
|
||||||
import Cookies from 'components/Cookies';
|
import Cookies from 'components/Cookies';
|
||||||
import ToolHint from 'components/ToolHint';
|
import ToolHint from 'components/ToolHint';
|
||||||
@ -185,7 +184,7 @@ const Sidebar = () => {
|
|||||||
Star
|
Star
|
||||||
</GitHubButton> */}
|
</GitHubButton> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.32.1</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.34.0</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
&.bruno-toast {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bruno-toast-card {
|
|
||||||
-webkit-animation-duration: 0.85s;
|
|
||||||
animation-duration: 0.85s;
|
|
||||||
-webkit-animation-delay: 0.1s;
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
position: relative;
|
|
||||||
max-width: calc(100% - var(--spacing-base-unit));
|
|
||||||
margin: 3vh 10vw;
|
|
||||||
|
|
||||||
animation: fade-and-slide-in-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-toast-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
position: relative;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
color: #721c24;
|
|
||||||
background-color: #f8d7da;
|
|
||||||
border-color: #f5c6cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-info {
|
|
||||||
color: #004085;
|
|
||||||
background-color: #cce5ff;
|
|
||||||
border-color: #b8daff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-warning {
|
|
||||||
color: #856404;
|
|
||||||
background-color: #fff3cd;
|
|
||||||
border-color: #ffeeba;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
color: #155724;
|
|
||||||
background-color: #d4edda;
|
|
||||||
border-color: #c3e6cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.closeToast {
|
|
||||||
cursor: pointer;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
@ -1,33 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
|
|
||||||
const ToastContent = ({ type, text, handleClose }) => (
|
|
||||||
<div className={`alert alert-${type}`} role="alert">
|
|
||||||
<div> {text} </div>
|
|
||||||
<div onClick={handleClose} className="closeToast">
|
|
||||||
<FontAwesomeIcon size="xs" icon={faTimes} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Toast = ({ text, type, duration, handleClose }) => {
|
|
||||||
let lifetime = duration ? duration : 3000;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (text) {
|
|
||||||
setTimeout(handleClose, lifetime);
|
|
||||||
}
|
|
||||||
}, [text]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper className="bruno-toast">
|
|
||||||
<div className="bruno-toast-card">
|
|
||||||
<ToastContent type={type} text={text} handleClose={handleClose}></ToastContent>
|
|
||||||
</div>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Toast;
|
|
@ -10,7 +10,8 @@ const ToolHint = ({
|
|||||||
tooltipStyle = {},
|
tooltipStyle = {},
|
||||||
place = 'top',
|
place = 'top',
|
||||||
offset,
|
offset,
|
||||||
theme = null
|
theme = null,
|
||||||
|
className = ''
|
||||||
}) => {
|
}) => {
|
||||||
const { theme: contextTheme } = useTheme();
|
const { theme: contextTheme } = useTheme();
|
||||||
const appliedTheme = theme || contextTheme;
|
const appliedTheme = theme || contextTheme;
|
||||||
@ -22,13 +23,14 @@ const ToolHint = ({
|
|||||||
...tooltipStyle,
|
...tooltipStyle,
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
padding: '0.25rem 0.5rem',
|
padding: '0.25rem 0.5rem',
|
||||||
|
zIndex: 9999,
|
||||||
backgroundColor: toolhintBackgroundColor,
|
backgroundColor: toolhintBackgroundColor,
|
||||||
color: toolhintTextColor
|
color: toolhintTextColor
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span id={toolhintId}>{children}</span>
|
<span id={toolhintId} className={className}>{children}</span>
|
||||||
<StyledWrapper theme={appliedTheme}>
|
<StyledWrapper theme={appliedTheme}>
|
||||||
<ReactToolHint
|
<ReactToolHint
|
||||||
anchorId={toolhintId}
|
anchorId={toolhintId}
|
||||||
|
@ -231,6 +231,11 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
.CodeMirror-brunoVarInfo p {
|
.CodeMirror-brunoVarInfo p {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hint-active {
|
||||||
|
background: #89f !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default GlobalStyle;
|
export default GlobalStyle;
|
||||||
|
@ -23,6 +23,8 @@ import '@fontsource/inter/600.css';
|
|||||||
import '@fontsource/inter/700.css';
|
import '@fontsource/inter/700.css';
|
||||||
import '@fontsource/inter/800.css';
|
import '@fontsource/inter/800.css';
|
||||||
import '@fontsource/inter/900.css';
|
import '@fontsource/inter/900.css';
|
||||||
|
import { setupPolyfills } from 'utils/common/setupPolyfills';
|
||||||
|
setupPolyfills();
|
||||||
|
|
||||||
function SafeHydrate({ children }) {
|
function SafeHydrate({ children }) {
|
||||||
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;
|
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;
|
||||||
|
@ -23,6 +23,7 @@ import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionWithUi
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { isElectron } from 'utils/common/platform';
|
import { isElectron } from 'utils/common/platform';
|
||||||
|
import { globalEnvironmentsUpdateEvent, updateGlobalEnvironments } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
|
||||||
const useIpcEvents = () => {
|
const useIpcEvents = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -109,6 +110,10 @@ const useIpcEvents = () => {
|
|||||||
dispatch(scriptEnvironmentUpdateEvent(val));
|
dispatch(scriptEnvironmentUpdateEvent(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeGlobalEnvironmentVariablesUpdateListener = ipcRenderer.on('main:global-environment-variables-update', (val) => {
|
||||||
|
dispatch(globalEnvironmentsUpdateEvent(val));
|
||||||
|
});
|
||||||
|
|
||||||
const removeCollectionRenamedListener = ipcRenderer.on('main:collection-renamed', (val) => {
|
const removeCollectionRenamedListener = ipcRenderer.on('main:collection-renamed', (val) => {
|
||||||
dispatch(collectionRenamedEvent(val));
|
dispatch(collectionRenamedEvent(val));
|
||||||
});
|
});
|
||||||
@ -149,6 +154,10 @@ const useIpcEvents = () => {
|
|||||||
dispatch(updateCookies(val));
|
dispatch(updateCookies(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeGlobalEnvironmentsUpdatesListener = ipcRenderer.on('main:load-global-environments', (val) => {
|
||||||
|
dispatch(updateGlobalEnvironments(val));
|
||||||
|
});
|
||||||
|
|
||||||
const removeSnapshotHydrationListener = ipcRenderer.on('main:hydrate-app-with-ui-state-snapshot', (val) => {
|
const removeSnapshotHydrationListener = ipcRenderer.on('main:hydrate-app-with-ui-state-snapshot', (val) => {
|
||||||
dispatch(hydrateCollectionWithUiStateSnapshot(val));
|
dispatch(hydrateCollectionWithUiStateSnapshot(val));
|
||||||
})
|
})
|
||||||
@ -159,6 +168,7 @@ const useIpcEvents = () => {
|
|||||||
removeCollectionAlreadyOpenedListener();
|
removeCollectionAlreadyOpenedListener();
|
||||||
removeDisplayErrorListener();
|
removeDisplayErrorListener();
|
||||||
removeScriptEnvUpdateListener();
|
removeScriptEnvUpdateListener();
|
||||||
|
removeGlobalEnvironmentVariablesUpdateListener();
|
||||||
removeCollectionRenamedListener();
|
removeCollectionRenamedListener();
|
||||||
removeRunFolderEventListener();
|
removeRunFolderEventListener();
|
||||||
removeRunRequestEventListener();
|
removeRunRequestEventListener();
|
||||||
@ -169,6 +179,7 @@ const useIpcEvents = () => {
|
|||||||
removePreferencesUpdatesListener();
|
removePreferencesUpdatesListener();
|
||||||
removeCookieUpdateListener();
|
removeCookieUpdateListener();
|
||||||
removeSystemProxyEnvUpdatesListener();
|
removeSystemProxyEnvUpdatesListener();
|
||||||
|
removeGlobalEnvironmentsUpdatesListener();
|
||||||
removeSnapshotHydrationListener();
|
removeSnapshotHydrationListener();
|
||||||
};
|
};
|
||||||
}, [isElectron]);
|
}, [isElectron]);
|
||||||
|
@ -60,7 +60,7 @@ const trackStart = () => {
|
|||||||
event: 'start',
|
event: 'start',
|
||||||
properties: {
|
properties: {
|
||||||
os: platformLib.os.family,
|
os: platformLib.os.family,
|
||||||
version: '1.32.1'
|
version: '1.34.0'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ import toast from 'react-hot-toast';
|
|||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import Mousetrap from 'mousetrap';
|
import Mousetrap from 'mousetrap';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import SaveRequest from 'components/RequestPane/SaveRequest';
|
|
||||||
import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
|
import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
|
||||||
import NetworkError from 'components/ResponsePane/NetworkError';
|
import NetworkError from 'components/ResponsePane/NetworkError';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
@ -20,19 +19,9 @@ export const HotkeysProvider = (props) => {
|
|||||||
const collections = useSelector((state) => state.collections.collections);
|
const collections = useSelector((state) => state.collections.collections);
|
||||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||||
const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen);
|
const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen);
|
||||||
const [showSaveRequestModal, setShowSaveRequestModal] = useState(false);
|
|
||||||
const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false);
|
const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false);
|
||||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||||
|
|
||||||
const getCurrentCollectionItems = () => {
|
|
||||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
|
||||||
if (activeTab) {
|
|
||||||
const collection = findCollectionByUid(collections, activeTab.collectionUid);
|
|
||||||
|
|
||||||
return collection ? collection.items : [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentCollection = () => {
|
const getCurrentCollection = () => {
|
||||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||||
if (activeTab) {
|
if (activeTab) {
|
||||||
@ -57,9 +46,6 @@ export const HotkeysProvider = (props) => {
|
|||||||
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
||||||
} else if (activeTab.type === 'collection-settings') {
|
} else if (activeTab.type === 'collection-settings') {
|
||||||
dispatch(saveCollectionRoot(collection.uid));
|
dispatch(saveCollectionRoot(collection.uid));
|
||||||
} else {
|
|
||||||
// todo: when ephermal requests go live
|
|
||||||
// setShowSaveRequestModal(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,9 +204,6 @@ export const HotkeysProvider = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HotkeysContext.Provider {...props} value="hotkey">
|
<HotkeysContext.Provider {...props} value="hotkey">
|
||||||
{showSaveRequestModal && (
|
|
||||||
<SaveRequest items={getCurrentCollectionItems()} onClose={() => setShowSaveRequestModal(false)} />
|
|
||||||
)}
|
|
||||||
{showEnvSettingsModal && (
|
{showEnvSettingsModal && (
|
||||||
<EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} />
|
<EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} />
|
||||||
)}
|
)}
|
||||||
|
@ -6,6 +6,7 @@ import appReducer from './slices/app';
|
|||||||
import collectionsReducer from './slices/collections';
|
import collectionsReducer from './slices/collections';
|
||||||
import tabsReducer from './slices/tabs';
|
import tabsReducer from './slices/tabs';
|
||||||
import notificationsReducer from './slices/notifications';
|
import notificationsReducer from './slices/notifications';
|
||||||
|
import globalEnvironmentsReducer from './slices/global-environments';
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
const { publicRuntimeConfig } = getConfig();
|
||||||
const isDevEnv = () => {
|
const isDevEnv = () => {
|
||||||
@ -22,7 +23,8 @@ export const store = configureStore({
|
|||||||
app: appReducer,
|
app: appReducer,
|
||||||
collections: collectionsReducer,
|
collections: collectionsReducer,
|
||||||
tabs: tabsReducer,
|
tabs: tabsReducer,
|
||||||
notifications: notificationsReducer
|
notifications: notificationsReducer,
|
||||||
|
globalEnvironments: globalEnvironmentsReducer
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
||||||
});
|
});
|
||||||
|
@ -44,6 +44,7 @@ import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index
|
|||||||
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
||||||
import { name } from 'file-loader';
|
import { name } from 'file-loader';
|
||||||
import slash from 'utils/common/slash';
|
import slash from 'utils/common/slash';
|
||||||
|
import { getGlobalEnvironmentVariables } from 'utils/collections/index';
|
||||||
import { findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index';
|
import { findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index';
|
||||||
|
|
||||||
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
|
||||||
@ -184,6 +185,7 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState)
|
|||||||
|
|
||||||
export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => {
|
export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -191,7 +193,11 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
|
|||||||
return reject(new Error('Collection not found'));
|
return reject(new Error('Collection not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
let collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
|
// add selected global env variables to the collection object
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
|
||||||
|
|
||||||
@ -213,6 +219,7 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
|
|||||||
|
|
||||||
export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -221,7 +228,11 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const itemCopy = cloneDeep(item || {});
|
const itemCopy = cloneDeep(item || {});
|
||||||
const collectionCopy = cloneDeep(collection);
|
let collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
|
// add selected global env variables to the collection object
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
|
||||||
sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
|
sendNetworkRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
|
||||||
@ -286,6 +297,7 @@ export const cancelRunnerExecution = (cancelTokenUid) => (dispatch) => {
|
|||||||
|
|
||||||
export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => {
|
export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
|
||||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -293,7 +305,12 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive, delay)
|
|||||||
return reject(new Error('Collection not found'));
|
return reject(new Error('Collection not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
let collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
|
// add selected global env variables to the collection object
|
||||||
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
|
||||||
const folder = findItemInCollection(collectionCopy, folderUid);
|
const folder = findItemInCollection(collectionCopy, folderUid);
|
||||||
|
|
||||||
if (folderUid && !folder) {
|
if (folderUid && !folder) {
|
||||||
|
@ -0,0 +1,242 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { stringifyIfNot, uuid } from 'utils/common/index';
|
||||||
|
import { environmentSchema } from '@usebruno/schema';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
globalEnvironments: [],
|
||||||
|
activeGlobalEnvironmentUid: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export const globalEnvironmentsSlice = createSlice({
|
||||||
|
name: 'global-environments',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
updateGlobalEnvironments: (state, action) => {
|
||||||
|
state.globalEnvironments = action.payload?.globalEnvironments;
|
||||||
|
state.activeGlobalEnvironmentUid = action.payload?.activeGlobalEnvironmentUid;
|
||||||
|
},
|
||||||
|
_addGlobalEnvironment: (state, action) => {
|
||||||
|
const { name, uid, variables = [] } = action.payload;
|
||||||
|
if (name?.length) {
|
||||||
|
state.globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_saveGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid, variables } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.variables = variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_renameGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid, name } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_copyGlobalEnvironment: (state, action) => {
|
||||||
|
const { name, uid, variables } = action.payload;
|
||||||
|
if (name?.length && uid) {
|
||||||
|
state.globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_selectGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: globalEnvironmentUid } = action.payload;
|
||||||
|
if (globalEnvironmentUid) {
|
||||||
|
const environment = state.globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
state.activeGlobalEnvironmentUid = globalEnvironmentUid;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.activeGlobalEnvironmentUid = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_deleteGlobalEnvironment: (state, action) => {
|
||||||
|
const { environmentUid: uid } = action.payload;
|
||||||
|
if (uid) {
|
||||||
|
state.globalEnvironments = state.globalEnvironments.filter(env => env?.uid !== uid);
|
||||||
|
if (uid === state.activeGlobalEnvironmentUid) {
|
||||||
|
state.activeGlobalEnvironmentUid = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
updateGlobalEnvironments,
|
||||||
|
_addGlobalEnvironment,
|
||||||
|
_saveGlobalEnvironment,
|
||||||
|
_renameGlobalEnvironment,
|
||||||
|
_copyGlobalEnvironment,
|
||||||
|
_selectGlobalEnvironment,
|
||||||
|
_deleteGlobalEnvironment
|
||||||
|
} = globalEnvironmentsSlice.actions;
|
||||||
|
|
||||||
|
export const addGlobalEnvironment = ({ name, variables = [] }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const uid = uuid();
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:create-global-environment', { name, uid, variables })
|
||||||
|
.then(
|
||||||
|
dispatch(_addGlobalEnvironment({ name, uid, variables }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyGlobalEnvironment = ({ name, environmentUid: baseEnvUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const baseEnv = globalEnvironments?.find(env => env?.uid == baseEnvUid)
|
||||||
|
const uid = uuid();
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:create-global-environment', { uid, name, variables: baseEnv.variables })
|
||||||
|
.then(() => {
|
||||||
|
dispatch(_copyGlobalEnvironment({ name, uid, variables: baseEnv.variables }))
|
||||||
|
})
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renameGlobalEnvironment = ({ name: newName, environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid)
|
||||||
|
if (!environment) {
|
||||||
|
return reject(new Error('Environment not found'));
|
||||||
|
}
|
||||||
|
environmentSchema
|
||||||
|
.validate(environment)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:rename-global-environment', { name: newName, environmentUid }))
|
||||||
|
.then(
|
||||||
|
dispatch(_renameGlobalEnvironment({ name: newName, environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveGlobalEnvironment = ({ variables, environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state.globalEnvironments.globalEnvironments;
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid);
|
||||||
|
|
||||||
|
if (!environment) {
|
||||||
|
return reject(new Error('Environment not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
environmentSchema
|
||||||
|
.validate(environment)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-global-environment', {
|
||||||
|
environmentUid,
|
||||||
|
variables
|
||||||
|
}))
|
||||||
|
.then(
|
||||||
|
dispatch(_saveGlobalEnvironment({ environmentUid, variables }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectGlobalEnvironment = ({ environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:select-global-environment', { environmentUid })
|
||||||
|
.then(
|
||||||
|
dispatch(_selectGlobalEnvironment({ environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteGlobalEnvironment = ({ environmentUid }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:delete-global-environment', { environmentUid })
|
||||||
|
.then(
|
||||||
|
dispatch(_deleteGlobalEnvironment({ environmentUid }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const globalEnvironmentsUpdateEvent = ({ globalEnvironmentVariables }) => (dispatch, getState) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!globalEnvironmentVariables) resolve();
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const globalEnvironments = state?.globalEnvironments?.globalEnvironments || [];
|
||||||
|
const environmentUid = state?.globalEnvironments?.activeGlobalEnvironmentUid;
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid == environmentUid);
|
||||||
|
|
||||||
|
if (!environment || !environmentUid) {
|
||||||
|
console.error('Global Environment not found');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let variables = cloneDeep(environment?.variables);
|
||||||
|
|
||||||
|
// update existing values
|
||||||
|
variables = variables?.map?.(variable => ({
|
||||||
|
...variable,
|
||||||
|
value: stringifyIfNot(globalEnvironmentVariables?.[variable?.name])
|
||||||
|
}));
|
||||||
|
|
||||||
|
// add new env values
|
||||||
|
Object.entries(globalEnvironmentVariables)?.forEach?.(([key, value]) => {
|
||||||
|
let isAnExistingVariable = variables?.find(v => v?.name == key)
|
||||||
|
if (!isAnExistingVariable) {
|
||||||
|
variables.push({
|
||||||
|
uid: uuid(),
|
||||||
|
name: key,
|
||||||
|
value: stringifyIfNot(value),
|
||||||
|
type: 'text',
|
||||||
|
secret: false,
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
environmentSchema
|
||||||
|
.validate(environment)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-global-environment', {
|
||||||
|
environmentUid,
|
||||||
|
variables
|
||||||
|
}))
|
||||||
|
.then(
|
||||||
|
dispatch(_saveGlobalEnvironment({ environmentUid, variables }))
|
||||||
|
)
|
||||||
|
.then(resolve)
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default globalEnvironmentsSlice.reducer;
|
@ -24,6 +24,51 @@
|
|||||||
--color-method-head: rgb(52 52 52);
|
--color-method-head: rgb(52 52 52);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal {
|
||||||
|
/* Required CSS variables after upgrading GraphiQL from v1.5.9 to v2.4.7 */
|
||||||
|
--color-primary: 0, 0%, 0% !important;
|
||||||
|
--color-secondary: 0, 0%, 0% !important;
|
||||||
|
--color-tertiary: 0, 0%, 0% !important;
|
||||||
|
--color-info: 0, 0%, 0% !important;
|
||||||
|
--color-success: 0, 0%, 0% !important;
|
||||||
|
--color-warning: 0, 0%, 0% !important;
|
||||||
|
--color-error: 0, 0%, 0% !important;
|
||||||
|
--color-neutral: 0, 0%, 0% !important;
|
||||||
|
--color-base: 0, 0%, 100% !important;
|
||||||
|
--alpha-secondary: .76;
|
||||||
|
--alpha-tertiary: .5;
|
||||||
|
--alpha-background-heavy: .15;
|
||||||
|
--alpha-background-medium: .1;
|
||||||
|
--alpha-background-light: .07;
|
||||||
|
--font-size-hint: .75rem;
|
||||||
|
--font-size-inline-code: .8125rem;
|
||||||
|
--font-size-body: .9375rem;
|
||||||
|
--font-size-h4: 1.125rem;
|
||||||
|
--font-size-h3: 1.375rem;
|
||||||
|
--font-size-h2: 1.8125rem;
|
||||||
|
--font-weight-regular: 400;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--line-height: 1.5;
|
||||||
|
--px-2: 2px;
|
||||||
|
--px-4: 4px;
|
||||||
|
--px-6: 6px;
|
||||||
|
--px-8: 8px;
|
||||||
|
--px-10: 10px;
|
||||||
|
--px-12: 12px;
|
||||||
|
--px-16: 16px;
|
||||||
|
--px-20: 20px;
|
||||||
|
--px-24: 24px;
|
||||||
|
--border-radius-2: 2px !important;
|
||||||
|
--border-radius-4: 2px !important;
|
||||||
|
--border-radius-8: 2px !important;
|
||||||
|
--border-radius-12: 2px !important;
|
||||||
|
--popover-box-shadow: 0px 0px 1px #000 !important;
|
||||||
|
--popover-border: none;
|
||||||
|
--sidebar-width: 60px;
|
||||||
|
--toolbar-width: 40px;
|
||||||
|
--session-header-height: 51px
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -786,6 +786,19 @@ export const getDefaultRequestPaneTab = (item) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGlobalEnvironmentVariables = ({ globalEnvironments, activeGlobalEnvironmentUid }) => {
|
||||||
|
let variables = {};
|
||||||
|
const environment = globalEnvironments?.find(env => env?.uid === activeGlobalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
each(environment.variables, (variable) => {
|
||||||
|
if (variable.name && variable.value && variable.enabled) {
|
||||||
|
variables[variable.name] = variable.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return variables;
|
||||||
|
};
|
||||||
|
|
||||||
export const getEnvironmentVariables = (collection) => {
|
export const getEnvironmentVariables = (collection) => {
|
||||||
let variables = {};
|
let variables = {};
|
||||||
if (collection) {
|
if (collection) {
|
||||||
@ -802,6 +815,7 @@ export const getEnvironmentVariables = (collection) => {
|
|||||||
return variables;
|
return variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getPathParams = (item) => {
|
const getPathParams = (item) => {
|
||||||
let pathParams = {};
|
let pathParams = {};
|
||||||
if (item && item.request && item.request.params) {
|
if (item && item.request && item.request.params) {
|
||||||
@ -828,14 +842,17 @@ export const getTotalRequestCountInCollection = (collection) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getAllVariables = (collection, item) => {
|
export const getAllVariables = (collection, item) => {
|
||||||
|
if(!collection) return {};
|
||||||
const envVariables = getEnvironmentVariables(collection);
|
const envVariables = getEnvironmentVariables(collection);
|
||||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||||
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
let { collectionVariables, folderVariables, requestVariables } = mergeVars(collection, requestTreePath);
|
||||||
const pathParams = getPathParams(item);
|
const pathParams = getPathParams(item);
|
||||||
|
const { globalEnvironmentVariables = {} } = collection;
|
||||||
|
|
||||||
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
const { processEnvVariables = {}, runtimeVariables = {} } = collection;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...globalEnvironmentVariables,
|
||||||
...collectionVariables,
|
...collectionVariables,
|
||||||
...envVariables,
|
...envVariables,
|
||||||
...folderVariables,
|
...folderVariables,
|
||||||
|
@ -158,3 +158,11 @@ export const humanizeDate = (dateString) => {
|
|||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateUidBasedOnHash = (str) => {
|
||||||
|
const hash = simpleHash(str);
|
||||||
|
|
||||||
|
return `${hash}`.padEnd(21, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringifyIfNot = v => typeof v === 'string' ? v : String(v);
|
||||||
|
24
packages/bruno-app/src/utils/common/setupPolyfills.js
Normal file
24
packages/bruno-app/src/utils/common/setupPolyfills.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export const setupPolyfills = () => {
|
||||||
|
// polyfill required to make react-pdf
|
||||||
|
if (typeof Promise.withResolvers === "undefined") {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.Promise.withResolvers = function () {
|
||||||
|
let resolve, reject
|
||||||
|
const promise = new Promise((res, rej) => {
|
||||||
|
resolve = res
|
||||||
|
reject = rej
|
||||||
|
})
|
||||||
|
return { promise, resolve, reject }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
global.Promise.withResolvers = function () {
|
||||||
|
let resolve, reject
|
||||||
|
const promise = new Promise((res, rej) => {
|
||||||
|
resolve = res
|
||||||
|
reject = rej
|
||||||
|
})
|
||||||
|
return { promise, resolve, reject }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ const readFile = (files) => {
|
|||||||
} catch (jsonError) {
|
} catch (jsonError) {
|
||||||
// not a valid JSOn, try yaml
|
// not a valid JSOn, try yaml
|
||||||
try {
|
try {
|
||||||
const parsedData = jsyaml.load(e.target.result);
|
const parsedData = jsyaml.load(e.target.result, { schema: jsyaml.CORE_SCHEMA });
|
||||||
resolve(parsedData);
|
resolve(parsedData);
|
||||||
} catch (yamlError) {
|
} catch (yamlError) {
|
||||||
console.error('Error parsing the file :', jsonError, yamlError);
|
console.error('Error parsing the file :', jsonError, yamlError);
|
||||||
@ -60,6 +60,7 @@ const addSuffixToDuplicateName = (item, index, allItems) => {
|
|||||||
const regexVariable = new RegExp('{{.*?}}', 'g');
|
const regexVariable = new RegExp('{{.*?}}', 'g');
|
||||||
|
|
||||||
const normalizeVariables = (value) => {
|
const normalizeVariables = (value) => {
|
||||||
|
value = value || '';
|
||||||
const variables = value.match(regexVariable) || [];
|
const variables = value.match(regexVariable) || [];
|
||||||
each(variables, (variable) => {
|
each(variables, (variable) => {
|
||||||
value = value.replace(variable, variable.replace('_.', '').replaceAll(' ', ''));
|
value = value.replace(variable, variable.replace('_.', '').replaceAll(' ', ''));
|
||||||
|
@ -373,7 +373,7 @@ const parseOpenApiCollection = (data) => {
|
|||||||
// Currently parsing of openapi spec is "do your best", that is
|
// Currently parsing of openapi spec is "do your best", that is
|
||||||
// allows "invalid" openapi spec
|
// allows "invalid" openapi spec
|
||||||
|
|
||||||
// assumes v3 if not defined. v2 no supported yet
|
// Assumes v3 if not defined. v2 is not supported yet
|
||||||
if (collectionData.openapi && !collectionData.openapi.startsWith('3')) {
|
if (collectionData.openapi && !collectionData.openapi.startsWith('3')) {
|
||||||
reject(new BrunoError('Only OpenAPI v3 is supported currently.'));
|
reject(new BrunoError('Only OpenAPI v3 is supported currently.'));
|
||||||
return;
|
return;
|
||||||
@ -382,7 +382,28 @@ const parseOpenApiCollection = (data) => {
|
|||||||
// TODO what if info.title not defined?
|
// TODO what if info.title not defined?
|
||||||
brunoCollection.name = collectionData.info.title;
|
brunoCollection.name = collectionData.info.title;
|
||||||
let servers = collectionData.servers || [];
|
let servers = collectionData.servers || [];
|
||||||
let baseUrl = servers[0] ? getDefaultUrl(servers[0]) : '';
|
|
||||||
|
// Create environments based on the servers
|
||||||
|
servers.forEach((server, index) => {
|
||||||
|
let baseUrl = getDefaultUrl(server);
|
||||||
|
let environmentName = server.description ? server.description : `Environment ${index + 1}`;
|
||||||
|
|
||||||
|
brunoCollection.environments.push({
|
||||||
|
uid: uuid(),
|
||||||
|
name: environmentName,
|
||||||
|
variables: [
|
||||||
|
{
|
||||||
|
uid: uuid(),
|
||||||
|
name: 'baseUrl',
|
||||||
|
value: baseUrl,
|
||||||
|
type: 'text',
|
||||||
|
enabled: true,
|
||||||
|
secret: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let securityConfig = getSecurity(collectionData);
|
let securityConfig = getSecurity(collectionData);
|
||||||
|
|
||||||
let allRequests = Object.entries(collectionData.paths)
|
let allRequests = Object.entries(collectionData.paths)
|
||||||
@ -399,7 +420,7 @@ const parseOpenApiCollection = (data) => {
|
|||||||
path: path.replace(/{([^}]+)}/g, ':$1'), // Replace placeholders enclosed in curly braces with colons
|
path: path.replace(/{([^}]+)}/g, ':$1'), // Replace placeholders enclosed in curly braces with colons
|
||||||
operationObject: operationObject,
|
operationObject: operationObject,
|
||||||
global: {
|
global: {
|
||||||
server: baseUrl,
|
server: '{{baseUrl}}',
|
||||||
security: securityConfig
|
security: securityConfig
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -97,6 +97,75 @@ const constructUrl = (url) => {
|
|||||||
|
|
||||||
let translationLog = {};
|
let translationLog = {};
|
||||||
|
|
||||||
|
/* struct of translation log
|
||||||
|
{
|
||||||
|
[collectionName]: {
|
||||||
|
script: [index1, index2],
|
||||||
|
test: [index1, index2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pushTranslationLog = (type, index) => {
|
||||||
|
if (!translationLog[i.name]) {
|
||||||
|
translationLog[i.name] = {};
|
||||||
|
}
|
||||||
|
if (!translationLog[i.name][type]) {
|
||||||
|
translationLog[i.name][type] = [];
|
||||||
|
}
|
||||||
|
translationLog[i.name][type].push(index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const importScriptsFromEvents = (events, requestObject, options, pushTranslationLog) => {
|
||||||
|
events.forEach((event) => {
|
||||||
|
if (event.script && event.script.exec) {
|
||||||
|
if (event.listen === 'prerequest') {
|
||||||
|
if (!requestObject.script) {
|
||||||
|
requestObject.script = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
|
||||||
|
requestObject.script.req = event.script.exec
|
||||||
|
.map((line, index) =>
|
||||||
|
options.enablePostmanTranslations.enabled
|
||||||
|
? postmanTranslation(line, () => pushTranslationLog('script', index))
|
||||||
|
: `// ${line}`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
} else if (typeof event.script.exec === 'string') {
|
||||||
|
requestObject.script.req = options.enablePostmanTranslations.enabled
|
||||||
|
? postmanTranslation(event.script.exec, () => pushTranslationLog('script', 0))
|
||||||
|
: `// ${event.script.exec}`;
|
||||||
|
} else {
|
||||||
|
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.listen === 'test') {
|
||||||
|
if (!requestObject.tests) {
|
||||||
|
requestObject.tests = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
|
||||||
|
requestObject.tests = event.script.exec
|
||||||
|
.map((line, index) =>
|
||||||
|
options.enablePostmanTranslations.enabled
|
||||||
|
? postmanTranslation(line, () => pushTranslationLog('test', index))
|
||||||
|
: `// ${line}`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
} else if (typeof event.script.exec === 'string') {
|
||||||
|
requestObject.tests = options.enablePostmanTranslations.enabled
|
||||||
|
? postmanTranslation(event.script.exec, () => pushTranslationLog('test', 0))
|
||||||
|
: `// ${event.script.exec}`;
|
||||||
|
} else {
|
||||||
|
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
|
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
|
||||||
brunoParent.items = brunoParent.items || [];
|
brunoParent.items = brunoParent.items || [];
|
||||||
const folderMap = {};
|
const folderMap = {};
|
||||||
@ -117,13 +186,35 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
|||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
name: folderName,
|
name: folderName,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
items: []
|
items: [],
|
||||||
|
root: {
|
||||||
|
meta: {
|
||||||
|
name: folderName
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
auth: {
|
||||||
|
mode: 'none',
|
||||||
|
basic: null,
|
||||||
|
bearer: null,
|
||||||
|
awsv4: null
|
||||||
|
},
|
||||||
|
headers: [],
|
||||||
|
script: {},
|
||||||
|
tests: '',
|
||||||
|
vars: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
brunoParent.items.push(brunoFolderItem);
|
|
||||||
folderMap[folderName] = brunoFolderItem;
|
|
||||||
if (i.item && i.item.length) {
|
if (i.item && i.item.length) {
|
||||||
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
|
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i.event) {
|
||||||
|
importScriptsFromEvents(i.event, brunoFolderItem.root.request, options, pushTranslationLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
brunoParent.items.push(brunoFolderItem);
|
||||||
|
folderMap[folderName] = brunoFolderItem;
|
||||||
} else {
|
} else {
|
||||||
if (i.request) {
|
if (i.request) {
|
||||||
const baseRequestName = i.name;
|
const baseRequestName = i.name;
|
||||||
@ -163,32 +254,14 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
|||||||
docs: i.request.description
|
docs: i.request.description
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/* struct of translation log
|
|
||||||
{
|
|
||||||
[collectionName]: {
|
|
||||||
script: [index1, index2],
|
|
||||||
test: [index1, index2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// type could be script or test
|
|
||||||
const pushTranslationLog = (type, index) => {
|
|
||||||
if (!translationLog[i.name]) {
|
|
||||||
translationLog[i.name] = {};
|
|
||||||
}
|
|
||||||
if (!translationLog[i.name][type]) {
|
|
||||||
translationLog[i.name][type] = [];
|
|
||||||
}
|
|
||||||
translationLog[i.name][type].push(index + 1);
|
|
||||||
};
|
|
||||||
if (i.event) {
|
if (i.event) {
|
||||||
i.event.forEach((event) => {
|
i.event.forEach((event) => {
|
||||||
if (event.listen === 'prerequest' && event.script && event.script.exec) {
|
if (event.listen === 'prerequest' && event.script && event.script.exec) {
|
||||||
if (!brunoRequestItem.request.script) {
|
if (!brunoRequestItem.request.script) {
|
||||||
brunoRequestItem.request.script = {};
|
brunoRequestItem.request.script = {};
|
||||||
}
|
}
|
||||||
if (Array.isArray(event.script.exec)) {
|
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
|
||||||
brunoRequestItem.request.script.req = event.script.exec
|
brunoRequestItem.request.script.req = event.script.exec
|
||||||
.map((line, index) =>
|
.map((line, index) =>
|
||||||
options.enablePostmanTranslations.enabled
|
options.enablePostmanTranslations.enabled
|
||||||
@ -196,17 +269,19 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
|||||||
: `// ${line}`
|
: `// ${line}`
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
} else {
|
} else if (typeof event.script.exec === 'string') {
|
||||||
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
|
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
|
||||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('script', 0))
|
? postmanTranslation(event.script.exec, () => pushTranslationLog('script', 0))
|
||||||
: `// ${event.script.exec[0]} `;
|
: `// ${event.script.exec}`;
|
||||||
|
} else {
|
||||||
|
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event.listen === 'test' && event.script && event.script.exec) {
|
if (event.listen === 'test' && event.script && event.script.exec) {
|
||||||
if (!brunoRequestItem.request.tests) {
|
if (!brunoRequestItem.request.tests) {
|
||||||
brunoRequestItem.request.tests = {};
|
brunoRequestItem.request.tests = {};
|
||||||
}
|
}
|
||||||
if (Array.isArray(event.script.exec)) {
|
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
|
||||||
brunoRequestItem.request.tests = event.script.exec
|
brunoRequestItem.request.tests = event.script.exec
|
||||||
.map((line, index) =>
|
.map((line, index) =>
|
||||||
options.enablePostmanTranslations.enabled
|
options.enablePostmanTranslations.enabled
|
||||||
@ -214,10 +289,12 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
|||||||
: `// ${line}`
|
: `// ${line}`
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
} else {
|
} else if (typeof event.script.exec === 'string') {
|
||||||
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
|
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
|
||||||
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('test', 0))
|
? postmanTranslation(event.script.exec, () => pushTranslationLog('test', 0))
|
||||||
: `// ${event.script.exec[0]} `;
|
: `// ${event.script.exec}`;
|
||||||
|
} else {
|
||||||
|
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -393,9 +470,30 @@ const importPostmanV2Collection = (collection, options) => {
|
|||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
version: '1',
|
version: '1',
|
||||||
items: [],
|
items: [],
|
||||||
environments: []
|
environments: [],
|
||||||
|
root: {
|
||||||
|
meta: {
|
||||||
|
name: collection.info.name
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
auth: {
|
||||||
|
mode: 'none',
|
||||||
|
basic: null,
|
||||||
|
bearer: null,
|
||||||
|
awsv4: null
|
||||||
|
},
|
||||||
|
headers: [],
|
||||||
|
script: {},
|
||||||
|
tests: '',
|
||||||
|
vars: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (collection.event) {
|
||||||
|
importScriptsFromEvents(collection.event, brunoCollection.root.request, options, pushTranslationLog);
|
||||||
|
}
|
||||||
|
|
||||||
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);
|
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);
|
||||||
|
|
||||||
return brunoCollection;
|
return brunoCollection;
|
||||||
|
@ -5,6 +5,8 @@ const replacements = {
|
|||||||
'pm\\.variables\\.set\\(': 'bru.setVar(',
|
'pm\\.variables\\.set\\(': 'bru.setVar(',
|
||||||
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
|
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
|
||||||
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
|
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
|
||||||
|
'pm\\.collectionVariables\\.has\\(': 'bru.hasVar(',
|
||||||
|
'pm\\.collectionVariables\\.unset\\(': 'bru.deleteVar(',
|
||||||
'pm\\.setNextRequest\\(': 'bru.setNextRequest(',
|
'pm\\.setNextRequest\\(': 'bru.setNextRequest(',
|
||||||
'pm\\.test\\(': 'test(',
|
'pm\\.test\\(': 'test(',
|
||||||
'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(',
|
'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(',
|
||||||
|
@ -108,12 +108,13 @@ export const isValidUrl = (url) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const interpolateUrl = ({ url, envVars, runtimeVariables, processEnvVars }) => {
|
export const interpolateUrl = ({ url, globalEnvironmentVariables = {}, envVars, runtimeVariables, processEnvVars }) => {
|
||||||
if (!url || !url.length || typeof url !== 'string') {
|
if (!url || !url.length || typeof url !== 'string') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return interpolate(url, {
|
return interpolate(url, {
|
||||||
|
...globalEnvironmentVariables,
|
||||||
...envVars,
|
...envVars,
|
||||||
...runtimeVariables,
|
...runtimeVariables,
|
||||||
process: {
|
process: {
|
||||||
|
@ -24,12 +24,12 @@
|
|||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "3.525.0",
|
"@aws-sdk/credential-providers": "3.658.1",
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
"@usebruno/js": "0.12.0",
|
"@usebruno/js": "0.12.0",
|
||||||
"@usebruno/lang": "0.12.0",
|
"@usebruno/lang": "0.12.0",
|
||||||
"aws4-axios": "^3.3.0",
|
"aws4-axios": "^3.3.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "1.7.5",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
@ -37,12 +37,11 @@
|
|||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"inquirer": "^9.1.4",
|
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"socks-proxy-agent": "^8.0.2",
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"vm2": "^3.9.13",
|
"@usebruno/vm2": "^3.9.13",
|
||||||
"xmlbuilder": "^15.1.1",
|
"xmlbuilder": "^15.1.1",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,13 @@
|
|||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@rollup/plugin-typescript": "^9.0.2",
|
"@rollup/plugin-typescript": "^9.0.2",
|
||||||
"rollup":"3.29.4",
|
"rollup":"3.29.5",
|
||||||
"rollup-plugin-dts": "^5.0.0",
|
"rollup-plugin-dts": "^5.0.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"rollup":"3.29.4"
|
"rollup":"3.29.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "v1.32.1",
|
"version": "v1.34.0",
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||||
"homepage": "https://www.usebruno.com",
|
"homepage": "https://www.usebruno.com",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"modulePaths": ["node_modules"]
|
"modulePaths": ["node_modules"]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "3.525.0",
|
"@aws-sdk/credential-providers": "3.658.1",
|
||||||
"@usebruno/common": "0.1.0",
|
"@usebruno/common": "0.1.0",
|
||||||
"@usebruno/js": "0.12.0",
|
"@usebruno/js": "0.12.0",
|
||||||
"@usebruno/lang": "0.12.0",
|
"@usebruno/lang": "0.12.0",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"@usebruno/schema": "0.7.0",
|
"@usebruno/schema": "0.7.0",
|
||||||
"about-window": "^1.15.2",
|
"about-window": "^1.15.2",
|
||||||
"aws4-axios": "^3.3.0",
|
"aws4-axios": "^3.3.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "1.7.5",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
@ -48,7 +48,6 @@
|
|||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-bigint": "^1.0.0",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
@ -56,7 +55,7 @@
|
|||||||
"socks-proxy-agent": "^8.0.2",
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"tough-cookie": "^4.1.3",
|
"tough-cookie": "^4.1.3",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vm2": "^3.9.13",
|
"@usebruno/vm2": "^3.9.13",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
@ -23,6 +23,7 @@ const registerPreferencesIpc = require('./ipc/preferences');
|
|||||||
const Watcher = require('./app/watcher');
|
const Watcher = require('./app/watcher');
|
||||||
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
|
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
|
||||||
const registerNotificationsIpc = require('./ipc/notifications');
|
const registerNotificationsIpc = require('./ipc/notifications');
|
||||||
|
const registerGlobalEnvironmentsIpc = require('./ipc/global-environments');
|
||||||
|
|
||||||
const lastOpenedCollections = new LastOpenedCollections();
|
const lastOpenedCollections = new LastOpenedCollections();
|
||||||
|
|
||||||
@ -143,6 +144,7 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
// register all ipc handlers
|
// register all ipc handlers
|
||||||
registerNetworkIpc(mainWindow);
|
registerNetworkIpc(mainWindow);
|
||||||
|
registerGlobalEnvironmentsIpc(mainWindow);
|
||||||
registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections);
|
registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections);
|
||||||
registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections);
|
registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections);
|
||||||
registerNotificationsIpc(mainWindow, watcher);
|
registerNotificationsIpc(mainWindow, watcher);
|
||||||
|
50
packages/bruno-electron/src/ipc/global-environments.js
Normal file
50
packages/bruno-electron/src/ipc/global-environments.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
require('dotenv').config();
|
||||||
|
const { ipcMain } = require('electron');
|
||||||
|
const { globalEnvironmentsStore } = require('../store/global-environments');
|
||||||
|
|
||||||
|
const registerGlobalEnvironmentsIpc = (mainWindow) => {
|
||||||
|
|
||||||
|
// GLOBAL ENVIRONMENTS
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:create-global-environment', async (event, { uid, name, variables }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.addGlobalEnvironment({ uid, name, variables });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:save-global-environment', async (event, { environmentUid, variables }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.saveGlobalEnvironment({ environmentUid, variables })
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:rename-global-environment', async (event, { environmentUid, name }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.renameGlobalEnvironment({ environmentUid, name });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:delete-global-environment', async (event, { environmentUid }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.deleteGlobalEnvironment({ environmentUid });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:select-global-environment', async (event, { environmentUid }) => {
|
||||||
|
try {
|
||||||
|
globalEnvironmentsStore.selectGlobalEnvironment({ environmentUid });
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = registerGlobalEnvironmentsIpc;
|
@ -233,6 +233,10 @@ const configureRequest = async (
|
|||||||
);
|
);
|
||||||
request.httpAgent = new HttpProxyAgent(proxyUri);
|
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
request.httpsAgent = new https.Agent({
|
||||||
|
...httpsAgentRequestFields
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (proxyMode === 'system') {
|
} else if (proxyMode === 'system') {
|
||||||
const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables();
|
const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables();
|
||||||
@ -257,6 +261,10 @@ const configureRequest = async (
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Invalid system https_proxy');
|
throw new Error('Invalid system https_proxy');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
request.httpsAgent = new https.Agent({
|
||||||
|
...httpsAgentRequestFields
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
||||||
request.httpsAgent = new https.Agent({
|
request.httpsAgent = new https.Agent({
|
||||||
@ -409,6 +417,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
requestUid,
|
requestUid,
|
||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:global-environment-variables-update', {
|
||||||
|
globalEnvironmentVariables: scriptResult.globalEnvironmentVariables
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// interpolate variables inside request
|
// interpolate variables inside request
|
||||||
@ -469,6 +481,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
requestUid,
|
requestUid,
|
||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:global-environment-variables-update', {
|
||||||
|
globalEnvironmentVariables: result.globalEnvironmentVariables
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -504,6 +520,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
requestUid,
|
requestUid,
|
||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:global-environment-variables-update', {
|
||||||
|
globalEnvironmentVariables: scriptResult.globalEnvironmentVariables
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return scriptResult;
|
return scriptResult;
|
||||||
};
|
};
|
||||||
@ -691,6 +711,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
requestUid,
|
requestUid,
|
||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:global-environment-variables-update', {
|
||||||
|
globalEnvironmentVariables: testResults.globalEnvironmentVariables
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1160,6 +1184,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
runtimeVariables: testResults.runtimeVariables,
|
runtimeVariables: testResults.runtimeVariables,
|
||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:global-environment-variables-update', {
|
||||||
|
globalEnvironmentVariables: testResults.globalEnvironmentVariables
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
|
@ -14,6 +14,7 @@ const getContentType = (headers = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
|
const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => {
|
||||||
|
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||||
const collectionVariables = request?.collectionVariables || {};
|
const collectionVariables = request?.collectionVariables || {};
|
||||||
const folderVariables = request?.folderVariables || {};
|
const folderVariables = request?.folderVariables || {};
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
@ -39,6 +40,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
|||||||
|
|
||||||
// runtimeVariables take precedence over envVars
|
// runtimeVariables take precedence over envVars
|
||||||
const combinedVars = {
|
const combinedVars = {
|
||||||
|
...globalEnvironmentVariables,
|
||||||
...collectionVariables,
|
...collectionVariables,
|
||||||
...envVariables,
|
...envVariables,
|
||||||
...folderVariables,
|
...folderVariables,
|
||||||
|
@ -370,6 +370,7 @@ const prepareRequest = (item, collection) => {
|
|||||||
mergeFolderLevelHeaders(request, requestTreePath);
|
mergeFolderLevelHeaders(request, requestTreePath);
|
||||||
mergeFolderLevelScripts(request, requestTreePath, scriptFlow);
|
mergeFolderLevelScripts(request, requestTreePath, scriptFlow);
|
||||||
mergeVars(collection, request, requestTreePath);
|
mergeVars(collection, request, requestTreePath);
|
||||||
|
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request level headers
|
// Request level headers
|
||||||
@ -461,6 +462,7 @@ const prepareRequest = (item, collection) => {
|
|||||||
axiosRequest.collectionVariables = request.collectionVariables;
|
axiosRequest.collectionVariables = request.collectionVariables;
|
||||||
axiosRequest.folderVariables = request.folderVariables;
|
axiosRequest.folderVariables = request.folderVariables;
|
||||||
axiosRequest.requestVariables = request.requestVariables;
|
axiosRequest.requestVariables = request.requestVariables;
|
||||||
|
axiosRequest.globalEnvironmentVariables = request.globalEnvironmentVariables;
|
||||||
axiosRequest.assertions = request.assertions;
|
axiosRequest.assertions = request.assertions;
|
||||||
|
|
||||||
return axiosRequest;
|
return axiosRequest;
|
||||||
|
@ -2,6 +2,7 @@ const { ipcMain } = require('electron');
|
|||||||
const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
|
const { getPreferences, savePreferences, preferencesUtil } = require('../store/preferences');
|
||||||
const { isDirectory } = require('../utils/filesystem');
|
const { isDirectory } = require('../utils/filesystem');
|
||||||
const { openCollection } = require('../app/collections');
|
const { openCollection } = require('../app/collections');
|
||||||
|
const { globalEnvironmentsStore } = require('../store/global-environments');
|
||||||
``;
|
``;
|
||||||
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
ipcMain.handle('renderer:ready', async (event) => {
|
ipcMain.handle('renderer:ready', async (event) => {
|
||||||
@ -9,10 +10,17 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
|
|||||||
const preferences = getPreferences();
|
const preferences = getPreferences();
|
||||||
mainWindow.webContents.send('main:load-preferences', preferences);
|
mainWindow.webContents.send('main:load-preferences', preferences);
|
||||||
|
|
||||||
|
// load system proxy vars
|
||||||
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
|
const systemProxyVars = preferencesUtil.getSystemProxyEnvVariables();
|
||||||
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
|
const { http_proxy, https_proxy, no_proxy } = systemProxyVars || {};
|
||||||
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
|
mainWindow.webContents.send('main:load-system-proxy-env', { http_proxy, https_proxy, no_proxy });
|
||||||
|
|
||||||
|
// load global environments
|
||||||
|
const globalEnvironments = globalEnvironmentsStore.getGlobalEnvironments();
|
||||||
|
let activeGlobalEnvironmentUid = globalEnvironmentsStore.getActiveGlobalEnvironmentUid();
|
||||||
|
activeGlobalEnvironmentUid = globalEnvironments?.find(env => env?.uid == activeGlobalEnvironmentUid) ? activeGlobalEnvironmentUid : null;
|
||||||
|
mainWindow.webContents.send('main:load-global-environments', { globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
|
|
||||||
// reload last opened collections
|
// reload last opened collections
|
||||||
const lastOpened = lastOpenedCollections.getAll();
|
const lastOpened = lastOpenedCollections.getAll();
|
||||||
|
|
||||||
|
132
packages/bruno-electron/src/store/global-environments.js
Normal file
132
packages/bruno-electron/src/store/global-environments.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const Store = require('electron-store');
|
||||||
|
const { encryptString, decryptString } = require('../utils/encryption');
|
||||||
|
|
||||||
|
class GlobalEnvironmentsStore {
|
||||||
|
constructor() {
|
||||||
|
this.store = new Store({
|
||||||
|
name: 'global-environments',
|
||||||
|
clearInvalidConfig: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidValue(val) {
|
||||||
|
return typeof val === 'string' && val.length >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptGlobalEnvironmentVariables({ globalEnvironments }) {
|
||||||
|
return globalEnvironments?.map(env => {
|
||||||
|
const variables = env.variables?.map(v => ({
|
||||||
|
...v,
|
||||||
|
value: v?.secret ? (this.isValidValue(v.value) ? encryptString(v.value) : '') : v?.value
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...env,
|
||||||
|
variables
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptGlobalEnvironmentVariables({ globalEnvironments }) {
|
||||||
|
return globalEnvironments?.map(env => {
|
||||||
|
const variables = env.variables?.map(v => ({
|
||||||
|
...v,
|
||||||
|
value: v?.secret ? (this.isValidValue(v.value) ? decryptString(v.value) : '') : v?.value
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...env,
|
||||||
|
variables
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getGlobalEnvironments() {
|
||||||
|
let globalEnvironments = this.store.get('environments', []);
|
||||||
|
globalEnvironments = this.decryptGlobalEnvironmentVariables({ globalEnvironments });
|
||||||
|
return globalEnvironments;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveGlobalEnvironmentUid() {
|
||||||
|
return this.store.get('activeGlobalEnvironmentUid', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobalEnvironments(globalEnvironments) {
|
||||||
|
globalEnvironments = this.encryptGlobalEnvironmentVariables({ globalEnvironments });
|
||||||
|
return this.store.set('environments', globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveGlobalEnvironmentUid(uid) {
|
||||||
|
return this.store.set('activeGlobalEnvironmentUid', uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGlobalEnvironment({ uid, name, variables = [] }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveGlobalEnvironment({ environmentUid: globalEnvironmentUid, variables }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.variables = variables;
|
||||||
|
}
|
||||||
|
globalEnvironments.push(environment);
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renameGlobalEnvironment({ environmentUid: globalEnvironmentUid, name }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
environment.name = name;
|
||||||
|
}
|
||||||
|
globalEnvironments.push(environment);
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyGlobalEnvironment({ uid, name, variables }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
globalEnvironments.push({
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
variables
|
||||||
|
});
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectGlobalEnvironment({ environmentUid: globalEnvironmentUid }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
const environment = globalEnvironments.find(env => env?.uid == globalEnvironmentUid);
|
||||||
|
if (environment) {
|
||||||
|
this.setActiveGlobalEnvironmentUid(globalEnvironmentUid);
|
||||||
|
} else {
|
||||||
|
this.setActiveGlobalEnvironmentUid(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGlobalEnvironment({ environmentUid }) {
|
||||||
|
let globalEnvironments = this.getGlobalEnvironments();
|
||||||
|
let activeGlobalEnvironmentUid = this.getActiveGlobalEnvironmentUid();
|
||||||
|
globalEnvironments = globalEnvironments.filter(env => env?.uid !== environmentUid);
|
||||||
|
if (environmentUid == activeGlobalEnvironmentUid) {
|
||||||
|
this.setActiveGlobalEnvironmentUid(null);
|
||||||
|
}
|
||||||
|
this.setGlobalEnvironments(globalEnvironments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalEnvironmentsStore = new GlobalEnvironmentsStore();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
globalEnvironmentsStore
|
||||||
|
};
|
@ -19,10 +19,10 @@
|
|||||||
"@types/react": "^18.0.25",
|
"@types/react": "^18.0.25",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "8.4.47",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"rollup":"3.29.4",
|
"rollup":"3.29.5",
|
||||||
"rollup-plugin-dts": "^5.0.0",
|
"rollup-plugin-dts": "^5.0.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
@ -34,6 +34,6 @@
|
|||||||
"markdown-it": "^13.0.1"
|
"markdown-it": "^13.0.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"rollup":"3.29.4"
|
"rollup":"3.29.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@n8n/vm2": "^3.9.23"
|
"@usebruno/vm2": "^3.9.13"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
|
"test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"atob": "^2.1.2",
|
"atob": "^2.1.2",
|
||||||
"axios": "^1.5.1",
|
"axios": "1.7.5",
|
||||||
"btoa": "^1.2.1",
|
"btoa": "^1.2.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-string": "^1.5.0",
|
"chai-string": "^1.5.0",
|
||||||
@ -38,16 +38,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"rollup": "3.2.5",
|
"rollup": "3.29.5",
|
||||||
"rollup-plugin-node-builtins": "^2.1.2",
|
|
||||||
"rollup-plugin-node-globals": "^1.4.0",
|
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"stream": "^0.0.2",
|
"stream": "^0.0.2",
|
||||||
"terser": "^5.31.1",
|
|
||||||
"uglify-js": "^3.18.0",
|
|
||||||
"util": "^0.12.5"
|
"util": "^0.12.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,14 @@ const { interpolate } = require('@usebruno/common');
|
|||||||
const variableNameRegex = /^[\w-.]*$/;
|
const variableNameRegex = /^[\w-.]*$/;
|
||||||
|
|
||||||
class Bru {
|
class Bru {
|
||||||
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables) {
|
constructor(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables) {
|
||||||
this.envVariables = envVariables || {};
|
this.envVariables = envVariables || {};
|
||||||
this.runtimeVariables = runtimeVariables || {};
|
this.runtimeVariables = runtimeVariables || {};
|
||||||
this.processEnvVars = cloneDeep(processEnvVars || {});
|
this.processEnvVars = cloneDeep(processEnvVars || {});
|
||||||
this.collectionVariables = collectionVariables || {};
|
this.collectionVariables = collectionVariables || {};
|
||||||
this.folderVariables = folderVariables || {};
|
this.folderVariables = folderVariables || {};
|
||||||
this.requestVariables = requestVariables || {};
|
this.requestVariables = requestVariables || {};
|
||||||
|
this.globalEnvironmentVariables = globalEnvironmentVariables || {};
|
||||||
this.collectionPath = collectionPath;
|
this.collectionPath = collectionPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ class Bru {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const combinedVars = {
|
const combinedVars = {
|
||||||
|
...this.globalEnvironmentVariables,
|
||||||
...this.collectionVariables,
|
...this.collectionVariables,
|
||||||
...this.envVariables,
|
...this.envVariables,
|
||||||
...this.folderVariables,
|
...this.folderVariables,
|
||||||
@ -63,6 +65,18 @@ class Bru {
|
|||||||
this.envVariables[key] = value;
|
this.envVariables[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGlobalEnvVar(key) {
|
||||||
|
return this._interpolate(this.globalEnvironmentVariables[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobalEnvVar(key, value) {
|
||||||
|
if (!key) {
|
||||||
|
throw new Error('Creating a env variable without specifying a name is not allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.globalEnvironmentVariables[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
hasVar(key) {
|
hasVar(key) {
|
||||||
return Object.hasOwn(this.runtimeVariables, key);
|
return Object.hasOwn(this.runtimeVariables, key);
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ const { interpolate } = require('@usebruno/common');
|
|||||||
|
|
||||||
const interpolateString = (
|
const interpolateString = (
|
||||||
str,
|
str,
|
||||||
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {} }
|
{ envVariables = {}, runtimeVariables = {}, processEnvVars = {}, collectionVariables = {}, folderVariables = {}, requestVariables = {}, globalEnvironmentVariables = {} }
|
||||||
) => {
|
) => {
|
||||||
if (!str || !str.length || typeof str !== 'string') {
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
const combinedVars = {
|
const combinedVars = {
|
||||||
|
...globalEnvironmentVariables,
|
||||||
...collectionVariables,
|
...collectionVariables,
|
||||||
...envVariables,
|
...envVariables,
|
||||||
...folderVariables,
|
...folderVariables,
|
||||||
|
@ -192,6 +192,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const interpolationContext = {
|
const interpolationContext = {
|
||||||
|
globalEnvironmentVariables: context.bru.globalEnvironmentVariables,
|
||||||
collectionVariables: context.bru.collectionVariables,
|
collectionVariables: context.bru.collectionVariables,
|
||||||
folderVariables: context.bru.folderVariables,
|
folderVariables: context.bru.folderVariables,
|
||||||
requestVariables: context.bru.requestVariables,
|
requestVariables: context.bru.requestVariables,
|
||||||
@ -240,6 +241,7 @@ class AssertRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
||||||
|
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||||
const collectionVariables = request?.collectionVariables || {};
|
const collectionVariables = request?.collectionVariables || {};
|
||||||
const folderVariables = request?.folderVariables || {};
|
const folderVariables = request?.folderVariables || {};
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
@ -255,7 +257,8 @@ class AssertRuntime {
|
|||||||
undefined,
|
undefined,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
folderVariables,
|
folderVariables,
|
||||||
requestVariables
|
requestVariables,
|
||||||
|
globalEnvironmentVariables
|
||||||
);
|
);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const res = createResponseParser(response);
|
const res = createResponseParser(response);
|
||||||
@ -267,6 +270,7 @@ class AssertRuntime {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
|
...globalEnvironmentVariables,
|
||||||
...collectionVariables,
|
...collectionVariables,
|
||||||
...envVariables,
|
...envVariables,
|
||||||
...folderVariables,
|
...folderVariables,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { NodeVM } = require('vm2');
|
const { NodeVM } = require('@usebruno/vm2');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
@ -47,10 +47,11 @@ class ScriptRuntime {
|
|||||||
processEnvVars,
|
processEnvVars,
|
||||||
scriptingConfig
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
|
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||||
const collectionVariables = request?.collectionVariables || {};
|
const collectionVariables = request?.collectionVariables || {};
|
||||||
const folderVariables = request?.folderVariables || {};
|
const folderVariables = request?.folderVariables || {};
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
@ -102,6 +103,7 @@ class ScriptRuntime {
|
|||||||
request,
|
request,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
runtimeVariables: cleanJson(runtimeVariables),
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -149,6 +151,7 @@ class ScriptRuntime {
|
|||||||
request,
|
request,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
runtimeVariables: cleanJson(runtimeVariables),
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -164,10 +167,11 @@ class ScriptRuntime {
|
|||||||
processEnvVars,
|
processEnvVars,
|
||||||
scriptingConfig
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
|
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||||
const collectionVariables = request?.collectionVariables || {};
|
const collectionVariables = request?.collectionVariables || {};
|
||||||
const folderVariables = request?.folderVariables || {};
|
const folderVariables = request?.folderVariables || {};
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const res = new BrunoResponse(response);
|
const res = new BrunoResponse(response);
|
||||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
@ -216,6 +220,7 @@ class ScriptRuntime {
|
|||||||
response,
|
response,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
runtimeVariables: cleanJson(runtimeVariables),
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -263,6 +268,7 @@ class ScriptRuntime {
|
|||||||
response,
|
response,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
runtimeVariables: cleanJson(runtimeVariables),
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const { NodeVM } = require('vm2');
|
const { NodeVM } = require('@usebruno/vm2');
|
||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
@ -48,10 +48,11 @@ class TestRuntime {
|
|||||||
processEnvVars,
|
processEnvVars,
|
||||||
scriptingConfig
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
|
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
|
||||||
const collectionVariables = request?.collectionVariables || {};
|
const collectionVariables = request?.collectionVariables || {};
|
||||||
const folderVariables = request?.folderVariables || {};
|
const folderVariables = request?.folderVariables || {};
|
||||||
const requestVariables = request?.requestVariables || {};
|
const requestVariables = request?.requestVariables || {};
|
||||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables);
|
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const res = new BrunoResponse(response);
|
const res = new BrunoResponse(response);
|
||||||
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
@ -81,12 +82,12 @@ class TestRuntime {
|
|||||||
request,
|
request,
|
||||||
envVariables,
|
envVariables,
|
||||||
runtimeVariables,
|
runtimeVariables,
|
||||||
|
globalEnvironmentVariables,
|
||||||
results: __brunoTestResults.getResults(),
|
results: __brunoTestResults.getResults(),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
test,
|
test,
|
||||||
bru,
|
bru,
|
||||||
@ -162,6 +163,7 @@ class TestRuntime {
|
|||||||
request,
|
request,
|
||||||
envVariables: cleanJson(envVariables),
|
envVariables: cleanJson(envVariables),
|
||||||
runtimeVariables: cleanJson(runtimeVariables),
|
runtimeVariables: cleanJson(runtimeVariables),
|
||||||
|
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
|
||||||
results: cleanJson(__brunoTestResults.getResults()),
|
results: cleanJson(__brunoTestResults.getResults()),
|
||||||
nextRequestName: bru.nextRequest
|
nextRequestName: bru.nextRequest
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,18 @@ const addBruShimToContext = (vm, bru) => {
|
|||||||
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
||||||
setEnvVar.dispose();
|
setEnvVar.dispose();
|
||||||
|
|
||||||
|
let getGlobalEnvVar = vm.newFunction('getGlobalEnvVar', function (key) {
|
||||||
|
return marshallToVm(bru.getGlobalEnvVar(vm.dump(key)), vm);
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'getGlobalEnvVar', getGlobalEnvVar);
|
||||||
|
getGlobalEnvVar.dispose();
|
||||||
|
|
||||||
|
let setGlobalEnvVar = vm.newFunction('setGlobalEnvVar', function (key, value) {
|
||||||
|
bru.setGlobalEnvVar(vm.dump(key), vm.dump(value));
|
||||||
|
});
|
||||||
|
vm.setProp(bruObject, 'setGlobalEnvVar', setGlobalEnvVar);
|
||||||
|
setGlobalEnvVar.dispose();
|
||||||
|
|
||||||
let hasVar = vm.newFunction('hasVar', function (key) {
|
let hasVar = vm.newFunction('hasVar', function (key) {
|
||||||
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
|
return marshallToVm(bru.hasVar(vm.dump(key)), vm);
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
const { marshallToVm } = require('../utils');
|
const { marshallToVm } = require('../utils');
|
||||||
|
|
||||||
const addBrunoResponseShimToContext = (vm, res) => {
|
const addBrunoResponseShimToContext = (vm, res) => {
|
||||||
const resObject = vm.newObject();
|
let resFn = vm.newFunction('res', function (exprStr) {
|
||||||
|
return marshallToVm(res(vm.dump(exprStr)), vm);
|
||||||
|
});
|
||||||
|
|
||||||
const status = marshallToVm(res?.status, vm);
|
const status = marshallToVm(res?.status, vm);
|
||||||
const headers = marshallToVm(res?.headers, vm);
|
const headers = marshallToVm(res?.headers, vm);
|
||||||
const body = marshallToVm(res?.body, vm);
|
const body = marshallToVm(res?.body, vm);
|
||||||
const responseTime = marshallToVm(res?.responseTime, vm);
|
const responseTime = marshallToVm(res?.responseTime, vm);
|
||||||
|
|
||||||
vm.setProp(resObject, 'status', status);
|
vm.setProp(resFn, 'status', status);
|
||||||
vm.setProp(resObject, 'headers', headers);
|
vm.setProp(resFn, 'headers', headers);
|
||||||
vm.setProp(resObject, 'body', body);
|
vm.setProp(resFn, 'body', body);
|
||||||
vm.setProp(resObject, 'responseTime', responseTime);
|
vm.setProp(resFn, 'responseTime', responseTime);
|
||||||
|
|
||||||
status.dispose();
|
status.dispose();
|
||||||
headers.dispose();
|
headers.dispose();
|
||||||
@ -21,35 +23,41 @@ const addBrunoResponseShimToContext = (vm, res) => {
|
|||||||
let getStatus = vm.newFunction('getStatus', function () {
|
let getStatus = vm.newFunction('getStatus', function () {
|
||||||
return marshallToVm(res.getStatus(), vm);
|
return marshallToVm(res.getStatus(), vm);
|
||||||
});
|
});
|
||||||
vm.setProp(resObject, 'getStatus', getStatus);
|
vm.setProp(resFn, 'getStatus', getStatus);
|
||||||
getStatus.dispose();
|
getStatus.dispose();
|
||||||
|
|
||||||
let getHeader = vm.newFunction('getHeader', function (name) {
|
let getHeader = vm.newFunction('getHeader', function (name) {
|
||||||
return marshallToVm(res.getHeader(vm.dump(name)), vm);
|
return marshallToVm(res.getHeader(vm.dump(name)), vm);
|
||||||
});
|
});
|
||||||
vm.setProp(resObject, 'getHeader', getHeader);
|
vm.setProp(resFn, 'getHeader', getHeader);
|
||||||
getHeader.dispose();
|
getHeader.dispose();
|
||||||
|
|
||||||
let getHeaders = vm.newFunction('getHeaders', function () {
|
let getHeaders = vm.newFunction('getHeaders', function () {
|
||||||
return marshallToVm(res.getHeaders(), vm);
|
return marshallToVm(res.getHeaders(), vm);
|
||||||
});
|
});
|
||||||
vm.setProp(resObject, 'getHeaders', getHeaders);
|
vm.setProp(resFn, 'getHeaders', getHeaders);
|
||||||
getHeaders.dispose();
|
getHeaders.dispose();
|
||||||
|
|
||||||
let getBody = vm.newFunction('getBody', function () {
|
let getBody = vm.newFunction('getBody', function () {
|
||||||
return marshallToVm(res.getBody(), vm);
|
return marshallToVm(res.getBody(), vm);
|
||||||
});
|
});
|
||||||
vm.setProp(resObject, 'getBody', getBody);
|
vm.setProp(resFn, 'getBody', getBody);
|
||||||
getBody.dispose();
|
getBody.dispose();
|
||||||
|
|
||||||
let getResponseTime = vm.newFunction('getResponseTime', function () {
|
let getResponseTime = vm.newFunction('getResponseTime', function () {
|
||||||
return marshallToVm(res.getResponseTime(), vm);
|
return marshallToVm(res.getResponseTime(), vm);
|
||||||
});
|
});
|
||||||
vm.setProp(resObject, 'getResponseTime', getResponseTime);
|
vm.setProp(resFn, 'getResponseTime', getResponseTime);
|
||||||
getResponseTime.dispose();
|
getResponseTime.dispose();
|
||||||
|
|
||||||
vm.setProp(vm.global, 'res', resObject);
|
let setBody = vm.newFunction('setBody', function (data) {
|
||||||
resObject.dispose();
|
res.setBody(vm.dump(data));
|
||||||
|
});
|
||||||
|
vm.setProp(resFn, 'setBody', setBody);
|
||||||
|
setBody.dispose();
|
||||||
|
|
||||||
|
vm.setProp(vm.global, 'res', resFn);
|
||||||
|
resFn.dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = addBrunoResponseShimToContext;
|
module.exports = addBrunoResponseShimToContext;
|
||||||
|
@ -21,13 +21,13 @@
|
|||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@rollup/plugin-typescript": "^9.0.2",
|
"@rollup/plugin-typescript": "^9.0.2",
|
||||||
"rollup":"3.29.4",
|
"rollup":"3.29.5",
|
||||||
"rollup-plugin-dts": "^5.0.0",
|
"rollup-plugin-dts": "^5.0.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"rollup":"3.29.4"
|
"rollup":"3.29.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,9 +25,7 @@ tests {
|
|||||||
const data = res.getBody();
|
const data = res.getBody();
|
||||||
expect(res.getBody()).to.eql({
|
expect(res.getBody()).to.eql({
|
||||||
"hello": {
|
"hello": {
|
||||||
"world": [
|
"world": ["bruno"]
|
||||||
"bruno"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/usebruno/bruno-testbench#readme",
|
"homepage": "https://github.com/usebruno/bruno-testbench#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "1.7.5",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "1.20.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.18.1",
|
"express": "4.21.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-xml-bodyparser": "^0.3.0",
|
"fast-xml-parser": "^4.5.0",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const xmlparser = require('express-xml-bodyparser');
|
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
|
const authRouter = require('./auth');
|
||||||
|
const echoRouter = require('./echo');
|
||||||
|
const xmlParser = require('./utils/xmlParser');
|
||||||
|
|
||||||
const app = new express();
|
const app = new express();
|
||||||
const port = process.env.PORT || 8080;
|
const port = process.env.PORT || 8080;
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(xmlparser());
|
app.use(xmlParser());
|
||||||
app.use(bodyParser.text());
|
app.use(bodyParser.text());
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
const authRouter = require('./auth');
|
|
||||||
const echoRouter = require('./echo');
|
|
||||||
|
|
||||||
app.use('/api/auth', authRouter);
|
app.use('/api/auth', authRouter);
|
||||||
app.use('/api/echo', echoRouter);
|
app.use('/api/echo', echoRouter);
|
||||||
|
|
||||||
|
30
packages/bruno-tests/src/utils/xmlParser.js
Normal file
30
packages/bruno-tests/src/utils/xmlParser.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const { XMLParser } = require('fast-xml-parser');
|
||||||
|
|
||||||
|
const xmlParser = () => {
|
||||||
|
const parser = new XMLParser({
|
||||||
|
ignoreAttributes: false,
|
||||||
|
allowBooleanAttributes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (req, res, next) => {
|
||||||
|
if (req.is('application/xml') || req.is('text/xml')) {
|
||||||
|
let data = '';
|
||||||
|
req.setEncoding('utf8');
|
||||||
|
req.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
req.body = parser.parse(data);
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
res.status(400).send('Invalid XML');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = xmlParser;
|
Loading…
Reference in New Issue
Block a user