mirror of
https://github.com/usebruno/bruno.git
synced 2025-02-15 09:19:28 +01:00
Merge branch 'main' into feature/random-data-placeholders
This commit is contained in:
commit
024b2b5c8c
5
.github/workflows/npm-bru-cli.yml
vendored
5
.github/workflows/npm-bru-cli.yml
vendored
@ -2,6 +2,11 @@ name: Bru CLI Tests (npm)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
build:
|
||||||
|
description: 'Test Bru CLI (npm)'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
|
||||||
# Assign permissions for unit tests to be reported.
|
# Assign permissions for unit tests to be reported.
|
||||||
# See https://github.com/dorny/test-reporter/issues/168
|
# See https://github.com/dorny/test-reporter/issues/168
|
||||||
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@ -52,6 +52,9 @@ jobs:
|
|||||||
cli-test:
|
cli-test:
|
||||||
name: CLI Tests
|
name: CLI Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
|
7974
package-lock.json
generated
7974
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -30,11 +30,13 @@
|
|||||||
"ts-jest": "^29.0.5"
|
"ts-jest": "^29.0.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"setup": "node ./scripts/setup.js",
|
||||||
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
|
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
|
||||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||||
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
||||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||||
|
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
|
||||||
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
|
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
|
||||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"presets": ["next/babel"],
|
"presets": ["@babel/preset-env"],
|
||||||
"plugins": [["styled-components", { "ssr": true }]]
|
"plugins": [["styled-components", { "ssr": true }]]
|
||||||
}
|
}
|
2
packages/bruno-app/.gitignore
vendored
2
packages/bruno-app/.gitignore
vendored
@ -31,6 +31,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
.next/
|
.next/
|
||||||
out/
|
dist/
|
||||||
|
|
||||||
.env
|
.env
|
@ -1,22 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
output: 'export',
|
|
||||||
reactStrictMode: false,
|
|
||||||
publicRuntimeConfig: {
|
|
||||||
CI: process.env.CI,
|
|
||||||
PLAYWRIGHT: process.env.PLAYWRIGHT,
|
|
||||||
ENV: process.env.ENV
|
|
||||||
},
|
|
||||||
webpack: (config, { isServer }) => {
|
|
||||||
// Fixes npm packages that depend on `fs` module
|
|
||||||
if (!isServer) {
|
|
||||||
config.resolve.fallback.fs = false;
|
|
||||||
}
|
|
||||||
Object.defineProperty(config, 'devtool', {
|
|
||||||
get() {
|
|
||||||
return 'source-map';
|
|
||||||
},
|
|
||||||
set() {},
|
|
||||||
});
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
|
@ -3,15 +3,15 @@
|
|||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENV=dev next dev -p 3000",
|
"dev": "rsbuild dev",
|
||||||
"build": "next build",
|
"build": "rsbuild build -m production",
|
||||||
"start": "next start",
|
"preview": "rsbuild preview",
|
||||||
"lint": "next lint",
|
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
||||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@fontsource/inter": "^5.0.15",
|
"@fontsource/inter": "^5.0.15",
|
||||||
"@prantlf/jsonlint": "^16.0.0",
|
"@prantlf/jsonlint": "^16.0.0",
|
||||||
"@reduxjs/toolkit": "^1.8.0",
|
"@reduxjs/toolkit": "^1.8.0",
|
||||||
@ -20,7 +20,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.7.5",
|
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "5.65.2",
|
"codemirror": "5.65.2",
|
||||||
"codemirror-graphql": "2.1.1",
|
"codemirror-graphql": "2.1.1",
|
||||||
@ -35,21 +34,20 @@
|
|||||||
"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",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "24.1.2",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.15",
|
||||||
"jsesc": "^3.0.2",
|
"jsesc": "^3.0.2",
|
||||||
"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",
|
||||||
"jsonpath-plus": "10.1.0",
|
"jsonpath-plus": "10.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.8",
|
||||||
"next": "14.2.16",
|
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pdfjs-dist": "4.4.168",
|
"pdfjs-dist": "4.4.168",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
@ -57,17 +55,17 @@
|
|||||||
"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",
|
||||||
"react": "18.2.0",
|
"react": "19.0.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"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": "19.0.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": "9.1.1",
|
"react-pdf": "9.1.1",
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.9",
|
||||||
"react-tooltip": "^5.5.2",
|
"react-tooltip": "^5.5.2",
|
||||||
"sass": "^1.46.0",
|
"sass": "^1.46.0",
|
||||||
"strip-json-comments": "^5.0.1",
|
"strip-json-comments": "^5.0.1",
|
||||||
@ -79,13 +77,14 @@
|
|||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.0",
|
"@rsbuild/core": "^1.1.2",
|
||||||
"@babel/plugin-transform-spread": "^7.16.7",
|
"@rsbuild/plugin-babel": "^1.0.3",
|
||||||
"@babel/preset-env": "^7.16.4",
|
"@rsbuild/plugin-node-polyfill": "^1.2.0",
|
||||||
"@babel/preset-react": "^7.16.0",
|
"@rsbuild/plugin-react": "^1.0.7",
|
||||||
"@babel/runtime": "^7.16.3",
|
"@rsbuild/plugin-sass": "^1.1.0",
|
||||||
|
"@rsbuild/plugin-styled-components": "1.1.0",
|
||||||
"autoprefixer": "10.4.20",
|
"autoprefixer": "10.4.20",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
39
packages/bruno-app/rsbuild.config.mjs
Normal file
39
packages/bruno-app/rsbuild.config.mjs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { defineConfig } from '@rsbuild/core';
|
||||||
|
import { pluginReact } from '@rsbuild/plugin-react';
|
||||||
|
import { pluginBabel } from '@rsbuild/plugin-babel';
|
||||||
|
import { pluginStyledComponents } from '@rsbuild/plugin-styled-components';
|
||||||
|
import { pluginSass } from '@rsbuild/plugin-sass';
|
||||||
|
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
pluginNodePolyfill(),
|
||||||
|
pluginReact(),
|
||||||
|
pluginStyledComponents(),
|
||||||
|
pluginSass(),
|
||||||
|
pluginBabel({
|
||||||
|
include: /\.(?:js|jsx|tsx)$/,
|
||||||
|
babelLoaderOptions(opts) {
|
||||||
|
opts.plugins?.unshift('babel-plugin-react-compiler');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
source: {
|
||||||
|
tsconfigPath: './jsconfig.json', // Specifies the path to the JavaScript/TypeScript configuration file
|
||||||
|
},
|
||||||
|
html: {
|
||||||
|
title: 'Bruno'
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
rspack: {
|
||||||
|
module: {
|
||||||
|
parser: {
|
||||||
|
javascript: {
|
||||||
|
// This loads the JavaScript contents from a library along with the main JavaScript bundle.
|
||||||
|
dynamicImportMode: "eager",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
@ -8,6 +8,8 @@ const StyledWrapper = styled.div`
|
|||||||
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
|
||||||
line-break: anywhere;
|
line-break: anywhere;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Removes the glow outline around the folded json */
|
/* Removes the glow outline around the folded json */
|
||||||
@ -26,6 +28,10 @@ const StyledWrapper = styled.div`
|
|||||||
|
|
||||||
.CodeMirror-dialog {
|
.CodeMirror-dialog {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
top: unset;
|
||||||
|
left: unset;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid #d3d6db;
|
border: 1px solid #d3d6db;
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { isEqual, escapeRegExp } from 'lodash';
|
import { isEqual, escapeRegExp } from 'lodash';
|
||||||
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 * as jsonlint from '@prantlf/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';
|
||||||
|
import { getAllVariables } from 'utils/collections';
|
||||||
|
|
||||||
let CodeMirror;
|
let CodeMirror;
|
||||||
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||||
@ -74,9 +74,16 @@ if (!SERVER_RENDERED) {
|
|||||||
'bru.setNextRequest(requestName)',
|
'bru.setNextRequest(requestName)',
|
||||||
'req.disableParsingResponseJson()',
|
'req.disableParsingResponseJson()',
|
||||||
'bru.getRequestVar(key)',
|
'bru.getRequestVar(key)',
|
||||||
|
'bru.runRequest(requestPathName)',
|
||||||
|
'bru.getAssertionResults()',
|
||||||
|
'bru.getTestResults()',
|
||||||
'bru.sleep(ms)',
|
'bru.sleep(ms)',
|
||||||
'bru.getGlobalEnvVar(key)',
|
'bru.getGlobalEnvVar(key)',
|
||||||
'bru.setGlobalEnvVar(key, value)'
|
'bru.setGlobalEnvVar(key, value)',
|
||||||
|
'bru.runner',
|
||||||
|
'bru.runner.setNextRequest(requestName)',
|
||||||
|
'bru.runner.skipRequest()',
|
||||||
|
'bru.runner.stopExecution()',
|
||||||
];
|
];
|
||||||
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
||||||
const cursor = editor.getCursor();
|
const cursor = editor.getCursor();
|
||||||
@ -98,7 +105,7 @@ if (!SERVER_RENDERED) {
|
|||||||
if (curWordBru) {
|
if (curWordBru) {
|
||||||
hintWords.forEach((h) => {
|
hintWords.forEach((h) => {
|
||||||
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
|
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
|
||||||
result.list.push(curWordBru.includes('.') ? h.split('.')[1] : h);
|
result.list.push(curWordBru.includes('.') ? h.split('.')?.at(-1) : h);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
result.list?.sort();
|
result.list?.sort();
|
||||||
@ -190,8 +197,20 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Cmd-Y': 'foldAll',
|
'Cmd-Y': 'foldAll',
|
||||||
'Ctrl-I': 'unfoldAll',
|
'Ctrl-I': 'unfoldAll',
|
||||||
'Cmd-I': 'unfoldAll',
|
'Cmd-I': 'unfoldAll',
|
||||||
'Ctrl-/': 'toggleComment',
|
'Ctrl-/': () => {
|
||||||
'Cmd-/': 'toggleComment'
|
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
|
||||||
|
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
|
||||||
|
} else {
|
||||||
|
this.editor.toggleComment();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Cmd-/': () => {
|
||||||
|
if (['application/ld+json', 'application/json'].includes(this.props.mode)) {
|
||||||
|
this.editor.toggleComment({ lineComment: '//', blockComment: '/*' });
|
||||||
|
} else {
|
||||||
|
this.editor.toggleComment();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
foldOptions: {
|
foldOptions: {
|
||||||
widget: (from, to) => {
|
widget: (from, to) => {
|
||||||
@ -289,7 +308,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
let variables = getEnvironmentVariables(this.props.collection);
|
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||||
if (!isEqual(variables, this.variables)) {
|
if (!isEqual(variables, this.variables)) {
|
||||||
this.addOverlay();
|
this.addOverlay();
|
||||||
}
|
}
|
||||||
@ -329,7 +348,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
addOverlay = () => {
|
addOverlay = () => {
|
||||||
const mode = this.props.mode || 'application/ld+json';
|
const mode = this.props.mode || 'application/ld+json';
|
||||||
let variables = getEnvironmentVariables(this.props.collection);
|
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
|
|
||||||
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
||||||
|
@ -79,6 +79,15 @@ const AuthMode = ({ collection }) => {
|
|||||||
>
|
>
|
||||||
Digest Auth
|
Digest Auth
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('ntlm');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NTLM Auth
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,110 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const NTLMAuth = ({ collection }) => {
|
||||||
|
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const ntlmAuth = get(collection, 'root.request.auth.ntlm', {});
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
|
||||||
|
const handleUsernameChange = (username) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: username,
|
||||||
|
password: ntlmAuth.password,
|
||||||
|
domain: ntlmAuth.domain
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = (password) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: ntlmAuth.username,
|
||||||
|
password: password,
|
||||||
|
domain: ntlmAuth.domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDomainChange = (domain) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: ntlmAuth.username,
|
||||||
|
password: ntlmAuth.password,
|
||||||
|
domain: domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Username</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.username || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleUsernameChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Password</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.password || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
isSecret={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Domain</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.domain || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleDomainChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NTLMAuth;
|
@ -1,5 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper = styled.div``;
|
const Wrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
@ -11,6 +11,8 @@ import ApiKeyAuth from './ApiKeyAuth/';
|
|||||||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import OAuth2 from './OAuth2';
|
import OAuth2 from './OAuth2';
|
||||||
|
import NTLMAuth from './NTLMAuth';
|
||||||
|
|
||||||
|
|
||||||
const Auth = ({ collection }) => {
|
const Auth = ({ collection }) => {
|
||||||
const authMode = get(collection, 'root.request.auth.mode');
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
@ -32,6 +34,9 @@ const Auth = ({ collection }) => {
|
|||||||
case 'digest': {
|
case 'digest': {
|
||||||
return <DigestAuth collection={collection} />;
|
return <DigestAuth collection={collection} />;
|
||||||
}
|
}
|
||||||
|
case 'ntlm': {
|
||||||
|
return <NTLMAuth collection={collection} />;
|
||||||
|
}
|
||||||
case 'oauth2': {
|
case 'oauth2': {
|
||||||
return <OAuth2 collection={collection} />;
|
return <OAuth2 collection={collection} />;
|
||||||
}
|
}
|
||||||
|
@ -68,12 +68,13 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getFile = (e) => {
|
const getFile = (e) => {
|
||||||
if (e.files?.[0]?.path) {
|
const filePath = window?.ipcRenderer?.getFilePath(e?.files?.[0]);
|
||||||
|
if (filePath) {
|
||||||
let relativePath;
|
let relativePath;
|
||||||
if (isWindowsOS()) {
|
if (isWindowsOS()) {
|
||||||
relativePath = slash(path.win32.relative(root, e.files[0].path));
|
relativePath = slash(path.win32.relative(root, filePath));
|
||||||
} else {
|
} else {
|
||||||
relativePath = path.posix.relative(root, e.files[0].path);
|
relativePath = path.posix.relative(root, filePath);
|
||||||
}
|
}
|
||||||
formik.setFieldValue(e.name, relativePath);
|
formik.setFieldValue(e.name, relativePath);
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,12 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
div.CodeMirror {
|
div.CodeMirror {
|
||||||
/* todo: find a better way */
|
|
||||||
height: calc(100vh - 240px);
|
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.editing-mode {
|
.editing-mode {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${(props) => props.theme.colors.text.yellow};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
|
|||||||
import Markdown from 'components/MarkDown';
|
import Markdown from 'components/MarkDown';
|
||||||
import CodeEditor from 'components/CodeEditor';
|
import CodeEditor from 'components/CodeEditor';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { IconEdit, IconX, IconFileText } from '@tabler/icons';
|
||||||
|
|
||||||
const Docs = ({ collection }) => {
|
const Docs = ({ collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -29,19 +30,50 @@ const Docs = ({ collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
|
const handleDiscardChanges = () => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionDocs({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
docs: docs
|
||||||
|
})
|
||||||
|
);
|
||||||
|
toggleViewMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = () => {
|
||||||
|
dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
toggleViewMode();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-1 h-full w-full relative">
|
<StyledWrapper className="mt-1 h-full w-full relative flex flex-col">
|
||||||
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
|
<div className='flex flex-row w-full justify-between items-center mb-4'>
|
||||||
{isEditing ? 'Preview' : 'Edit'}
|
<div className='text-lg font-medium flex items-center gap-2'>
|
||||||
|
<IconFileText size={20} strokeWidth={1.5} />
|
||||||
|
Documentation
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row gap-2 items-center justify-center'>
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
|
||||||
|
<IconX className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
|
||||||
|
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
collection={collection}
|
collection={collection}
|
||||||
theme={displayedTheme}
|
theme={displayedTheme}
|
||||||
value={docs || ''}
|
value={docs}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
mode="application/text"
|
mode="application/text"
|
||||||
@ -49,10 +81,44 @@ const Docs = ({ collection }) => {
|
|||||||
fontSize={get(preferences, 'font.codeFontSize')}
|
fontSize={get(preferences, 'font.codeFontSize')}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<div className='h-full overflow-auto pl-1'>
|
||||||
|
<div className='h-[1px] min-h-[500px]'>
|
||||||
|
{
|
||||||
|
docs?.length > 0 ?
|
||||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||||
|
:
|
||||||
|
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={documentationPlaceholder} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Docs;
|
export default Docs;
|
||||||
|
|
||||||
|
|
||||||
|
const documentationPlaceholder = `
|
||||||
|
Welcome to your collection documentation! This space is designed to help you document your API collection effectively.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Use this section to provide a high-level overview of your collection. You can describe:
|
||||||
|
- The purpose of these API endpoints
|
||||||
|
- Key features and functionalities
|
||||||
|
- Target audience or users
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- Keep documentation up to date
|
||||||
|
- Include request/response examples
|
||||||
|
- Document error scenarios
|
||||||
|
- Add relevant links and references
|
||||||
|
|
||||||
|
## Markdown Support
|
||||||
|
This documentation supports Markdown formatting! You can use:
|
||||||
|
- **Bold** and *italic* text
|
||||||
|
- \`code blocks\` and syntax highlighting
|
||||||
|
- Tables and lists
|
||||||
|
- [Links](https://example.com)
|
||||||
|
- And more!
|
||||||
|
`;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
|
||||||
import { getTotalRequestCountInCollection } from 'utils/collections/';
|
|
||||||
|
|
||||||
const Info = ({ collection }) => {
|
|
||||||
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledWrapper className="w-full flex flex-col h-full">
|
|
||||||
<div className="text-xs mb-4 text-muted">General information about the collection.</div>
|
|
||||||
<table className="w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr className="">
|
|
||||||
<td className="py-2 px-2 text-right">Name :</td>
|
|
||||||
<td className="py-2 px-2">{collection.name}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="">
|
|
||||||
<td className="py-2 px-2 text-right">Location :</td>
|
|
||||||
<td className="py-2 px-2 break-all">{collection.pathname}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="">
|
|
||||||
<td className="py-2 px-2 text-right">Ignored files :</td>
|
|
||||||
<td className="py-2 px-2 break-all">{collection.brunoConfig?.ignore?.map((x) => `'${x}'`).join(', ')}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="">
|
|
||||||
<td className="py-2 px-2 text-right">Environments :</td>
|
|
||||||
<td className="py-2 px-2">{collection.environments?.length || 0}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="">
|
|
||||||
<td className="py-2 px-2 text-right">Requests :</td>
|
|
||||||
<td className="py-2 px-2">{totalRequestsInCollection}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</StyledWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Info;
|
|
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { getTotalRequestCountInCollection } from 'utils/collections/';
|
||||||
|
import { IconFolder, IconFileOff, IconWorld, IconApi } from '@tabler/icons';
|
||||||
|
|
||||||
|
const Info = ({ collection }) => {
|
||||||
|
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col h-fit">
|
||||||
|
<div className="rounded-lg py-6">
|
||||||
|
<div className="grid gap-6">
|
||||||
|
{/* Location Row */}
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
|
<IconFolder className="w-5 h-5 text-blue-500" stroke={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
<div className="font-semibold text-sm">Location</div>
|
||||||
|
<div className="mt-1 text-sm text-muted break-all">
|
||||||
|
{collection.pathname}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Environments Row */}
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||||
|
<IconWorld className="w-5 h-5 text-green-500" stroke={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
<div className="font-semibold text-sm">Environments</div>
|
||||||
|
<div className="mt-1 text-sm text-muted">
|
||||||
|
{collection.environments?.length || 0} environment{collection.environments?.length !== 1 ? 's' : ''} configured
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Requests Row */}
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0 p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||||
|
<IconApi className="w-5 h-5 text-purple-500" stroke={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
<div className="font-semibold text-sm">Requests</div>
|
||||||
|
<div className="mt-1 text-sm text-muted">
|
||||||
|
{totalRequestsInCollection} request{totalRequestsInCollection !== 1 ? 's' : ''} in collection
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Info;
|
@ -0,0 +1,25 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
&.card {
|
||||||
|
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||||
|
|
||||||
|
.title {
|
||||||
|
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||||
|
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||||
|
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
|
||||||
|
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
thead {
|
||||||
|
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
|
||||||
|
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { flattenItems } from "utils/collections";
|
||||||
|
import { IconAlertTriangle } from '@tabler/icons';
|
||||||
|
import StyledWrapper from "./StyledWrapper";
|
||||||
|
|
||||||
|
const RequestsNotLoaded = ({ collection }) => {
|
||||||
|
const flattenedItems = flattenItems(collection.items);
|
||||||
|
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
|
||||||
|
|
||||||
|
if (!itemsFailedLoading?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full card my-2">
|
||||||
|
<div className="flex items-center gap-2 px-3 py-2 title bg-yellow-50 dark:bg-yellow-900/20">
|
||||||
|
<IconAlertTriangle size={16} className="text-yellow-500" />
|
||||||
|
<span className="font-medium">Following requests were not loaded</span>
|
||||||
|
</div>
|
||||||
|
<table className="w-full border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="py-2 px-3 text-left font-medium">
|
||||||
|
Pathname
|
||||||
|
</th>
|
||||||
|
<th className="py-2 px-3 text-left font-medium">
|
||||||
|
Size
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{flattenedItems?.map((item, index) => (
|
||||||
|
item?.partial && !item?.loading ? (
|
||||||
|
<tr key={index}>
|
||||||
|
<td className="py-1.5 px-3">
|
||||||
|
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
|
||||||
|
</td>
|
||||||
|
<td className="py-1.5 px-3">
|
||||||
|
{item?.size?.toFixed?.(2)} MB
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : null
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestsNotLoaded;
|
@ -0,0 +1,25 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
.partial {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completed {
|
||||||
|
color: ${(props) => props.theme.colors.text.green};
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
color: ${(props) => props.theme.colors.text.danger};
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,27 @@
|
|||||||
|
import StyledWrapper from "./StyledWrapper";
|
||||||
|
import Docs from "../Docs";
|
||||||
|
import Info from "./Info";
|
||||||
|
import { IconBox } from '@tabler/icons';
|
||||||
|
import RequestsNotLoaded from "./RequestsNotLoaded";
|
||||||
|
|
||||||
|
const Overview = ({ collection }) => {
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<div className="grid grid-cols-5 gap-4 h-full">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<div className="text-xl font-semibold flex items-center gap-2">
|
||||||
|
<IconBox size={24} stroke={1.5} />
|
||||||
|
{collection?.name}
|
||||||
|
</div>
|
||||||
|
<Info collection={collection} />
|
||||||
|
<RequestsNotLoaded collection={collection} />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3">
|
||||||
|
<Docs collection={collection} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Overview;
|
@ -1,6 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
.settings-label {
|
.settings-label {
|
||||||
width: 110px;
|
width: 110px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
div.CodeMirror {
|
div.CodeMirror {
|
||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
max-width: 800px;
|
|
||||||
|
|
||||||
div.tabs {
|
div.tabs {
|
||||||
div.tab {
|
div.tab {
|
||||||
padding: 6px 0px;
|
padding: 6px 0px;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div``;
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
div.title {
|
div.title {
|
||||||
color: var(--color-tab-inactive);
|
color: var(--color-tab-inactive);
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,11 @@ import Headers from './Headers';
|
|||||||
import Auth from './Auth';
|
import Auth from './Auth';
|
||||||
import Script from './Script';
|
import Script from './Script';
|
||||||
import Test from './Tests';
|
import Test from './Tests';
|
||||||
import Docs from './Docs';
|
|
||||||
import Presets from './Presets';
|
import Presets from './Presets';
|
||||||
import Info from './Info';
|
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import Vars from './Vars/index';
|
import Vars from './Vars/index';
|
||||||
import DotIcon from 'components/Icons/Dot';
|
import DotIcon from 'components/Icons/Dot';
|
||||||
|
import Overview from './Overview/index';
|
||||||
|
|
||||||
const ContentIndicator = () => {
|
const ContentIndicator = () => {
|
||||||
return (
|
return (
|
||||||
@ -97,6 +96,9 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
|
|
||||||
const getTabPanel = (tab) => {
|
const getTabPanel = (tab) => {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
|
case 'overview': {
|
||||||
|
return <Overview collection={collection} />;
|
||||||
|
}
|
||||||
case 'headers': {
|
case 'headers': {
|
||||||
return <Headers collection={collection} />;
|
return <Headers collection={collection} />;
|
||||||
}
|
}
|
||||||
@ -128,12 +130,6 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case 'docs': {
|
|
||||||
return <Docs collection={collection} />;
|
|
||||||
}
|
|
||||||
case 'info': {
|
|
||||||
return <Info collection={collection} />;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,6 +142,9 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||||
|
<div className={getTabClassname('overview')} role="tab" onClick={() => setTab('overview')}>
|
||||||
|
Overview
|
||||||
|
</div>
|
||||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||||
Headers
|
Headers
|
||||||
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
|
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
|
||||||
@ -177,13 +176,6 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
Client Certificates
|
Client Certificates
|
||||||
{clientCertConfig.length > 0 && <ContentIndicator />}
|
{clientCertConfig.length > 0 && <ContentIndicator />}
|
||||||
</div>
|
</div>
|
||||||
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
|
|
||||||
Docs
|
|
||||||
{hasDocs && <ContentIndicator />}
|
|
||||||
</div>
|
|
||||||
<div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}>
|
|
||||||
Info
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
|
<section className="mt-4 h-full">{getTabPanel(tab)}</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -3,7 +3,6 @@ import styled from 'styled-components';
|
|||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
.editing-mode {
|
.editing-mode {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${(props) => props.theme.colors.text.yellow};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ const EnvironmentSelector = ({ collection }) => {
|
|||||||
const Icon = forwardRef((props, ref) => {
|
const Icon = forwardRef((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||||
{activeEnvironment ? activeEnvironment.name : 'No Environment'}
|
<p className="text-nowrap truncate max-w-32">{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
|
||||||
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -78,7 +78,10 @@ const EnvironmentSelector = ({ collection }) => {
|
|||||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||||
<span className="ml-2">No Environment</span>
|
<span className="ml-2">No Environment</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
|
<div className="dropdown-item border-top" onClick={() => {
|
||||||
|
handleSettingsIconClick();
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
}}>
|
||||||
<div className="pr-2 text-gray-600">
|
<div className="pr-2 text-gray-600">
|
||||||
<IconSettings size={18} strokeWidth={1.5} />
|
<IconSettings size={18} strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,6 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
|
|
||||||
// todo: Add this to global env too.
|
|
||||||
const validateEnvironmentName = (name) => {
|
const validateEnvironmentName = (name) => {
|
||||||
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
|
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useRef, useEffect } from 'react';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { IconTrash, IconAlertCircle } from '@tabler/icons';
|
import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCheck } from '@tabler/icons';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
@ -13,7 +14,7 @@ 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';
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
|
||||||
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
|
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
const addButtonRef = useRef(null);
|
const addButtonRef = useRef(null);
|
||||||
@ -84,6 +85,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
formik.setFieldValue(formik.values.length, newVariable, false);
|
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onActivate = () => {
|
||||||
|
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
|
||||||
|
.then(() => {
|
||||||
|
if (environment) {
|
||||||
|
toast.success(`Environment changed to ${environment.name}`);
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
toast.success(`No Environments are active now`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemoveVar = (id) => {
|
const handleRemoveVar = (id) => {
|
||||||
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||||
};
|
};
|
||||||
@ -183,13 +197,19 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="flex items-center">
|
||||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
<button type="submit" className="submit btn btn-sm btn-secondary mt-2 flex items-center" onClick={formik.handleSubmit}>
|
||||||
|
<IconDeviceFloppy size={16} strokeWidth={1.5} className="mr-1" />
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
|
<button type="submit" className="ml-2 px-1 submit btn btn-sm btn-close mt-2 flex items-center" onClick={handleReset}>
|
||||||
|
<IconRefresh size={16} strokeWidth={1.5} className="mr-1" />
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-close mt-2 flex items-center" onClick={onActivate}>
|
||||||
|
<IconCircleCheck size={16} strokeWidth={1.5} className="mr-1" />
|
||||||
|
Activate
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import DeleteEnvironment from '../../DeleteEnvironment';
|
|||||||
import RenameEnvironment from '../../RenameEnvironment';
|
import RenameEnvironment from '../../RenameEnvironment';
|
||||||
import EnvironmentVariables from './EnvironmentVariables';
|
import EnvironmentVariables from './EnvironmentVariables';
|
||||||
|
|
||||||
const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
|
const EnvironmentDetails = ({ environment, collection, setIsModified, onClose }) => {
|
||||||
const [openEditModal, setOpenEditModal] = useState(false);
|
const [openEditModal, setOpenEditModal] = useState(false);
|
||||||
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
const [openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||||
const [openCopyModal, setOpenCopyModal] = useState(false);
|
const [openCopyModal, setOpenCopyModal] = useState(false);
|
||||||
@ -38,7 +38,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} />
|
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} onClose={onClose} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-left: solid 2px transparent;
|
border-left: solid 2px transparent;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -8,8 +8,10 @@ import ImportEnvironment from '../ImportEnvironment';
|
|||||||
import ManageSecrets from '../ManageSecrets';
|
import ManageSecrets from '../ManageSecrets';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||||
|
import ToolHint from 'components/ToolHint';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified }) => {
|
const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified, onClose }) => {
|
||||||
const { environments } = collection;
|
const { environments } = collection;
|
||||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||||
const [openImportModal, setOpenImportModal] = useState(false);
|
const [openImportModal, setOpenImportModal] = useState(false);
|
||||||
@ -23,6 +25,11 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEnvironment) {
|
if (selectedEnvironment) {
|
||||||
|
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
|
||||||
|
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
|
||||||
|
if (hasSelectedEnvironmentChanged) {
|
||||||
|
setSelectedEnvironment(_selectedEnvironment);
|
||||||
|
}
|
||||||
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
setOriginalEnvironmentVariables(selectedEnvironment.variables);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -103,13 +110,15 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
|||||||
{environments &&
|
{environments &&
|
||||||
environments.length &&
|
environments.length &&
|
||||||
environments.map((env) => (
|
environments.map((env) => (
|
||||||
|
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
|
||||||
<div
|
<div
|
||||||
key={env.uid}
|
id={env.uid}
|
||||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||||
>
|
>
|
||||||
<span className="break-all">{env.name}</span>
|
<span className="break-all">{env.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</ToolHint>
|
||||||
))}
|
))}
|
||||||
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||||
+ <span>Create</span>
|
+ <span>Create</span>
|
||||||
@ -132,6 +141,7 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
|||||||
collection={collection}
|
collection={collection}
|
||||||
setIsModified={setIsModified}
|
setIsModified={setIsModified}
|
||||||
originalEnvironmentVariables={originalEnvironmentVariables}
|
originalEnvironmentVariables={originalEnvironmentVariables}
|
||||||
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -72,6 +72,7 @@ const EnvironmentSettings = ({ collection, onClose }) => {
|
|||||||
collection={collection}
|
collection={collection}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
setIsModified={setIsModified}
|
setIsModified={setIsModified}
|
||||||
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
table {
|
.editing-mode {
|
||||||
td {
|
cursor: pointer;
|
||||||
&:first-child {
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
import 'github-markdown-css/github-markdown.css';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { updateFolderDocs } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import Markdown from 'components/MarkDown';
|
||||||
|
import CodeEditor from 'components/CodeEditor';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Documentation = ({ collection, folder }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { displayedTheme } = useTheme();
|
||||||
|
const preferences = useSelector((state) => state.app.preferences);
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const docs = get(folder, 'root.docs', '');
|
||||||
|
|
||||||
|
const toggleViewMode = () => {
|
||||||
|
setIsEditing((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateFolderDocs({
|
||||||
|
folderUid: folder.uid,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
docs: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-1 h-full w-full relative flex flex-col">
|
||||||
|
<div className="editing-mode flex justify-between items-center" role="tab" onClick={toggleViewMode}>
|
||||||
|
{isEditing ? 'Preview' : 'Edit'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isEditing ? (
|
||||||
|
<div className="mt-2 flex-1 max-h-[70vh]">
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
theme={displayedTheme}
|
||||||
|
value={docs || ''}
|
||||||
|
onEdit={onEdit}
|
||||||
|
onSave={onSave}
|
||||||
|
mode="application/text"
|
||||||
|
/>
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary my-6" onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||||
|
)}
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Documentation;
|
@ -7,6 +7,7 @@ import Script from './Script';
|
|||||||
import Tests from './Tests';
|
import Tests from './Tests';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import Vars from './Vars';
|
import Vars from './Vars';
|
||||||
|
import Documentation from './Documentation';
|
||||||
import DotIcon from 'components/Icons/Dot';
|
import DotIcon from 'components/Icons/Dot';
|
||||||
|
|
||||||
const ContentIndicator = () => {
|
const ContentIndicator = () => {
|
||||||
@ -60,6 +61,9 @@ const FolderSettings = ({ collection, folder }) => {
|
|||||||
case 'vars': {
|
case 'vars': {
|
||||||
return <Vars collection={collection} folder={folder} />;
|
return <Vars collection={collection} folder={folder} />;
|
||||||
}
|
}
|
||||||
|
case 'docs': {
|
||||||
|
return <Documentation collection={collection} folder={folder} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +93,9 @@ const FolderSettings = ({ collection, folder }) => {
|
|||||||
Vars
|
Vars
|
||||||
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
|
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
|
||||||
|
Docs
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>
|
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@ const EnvironmentSelector = () => {
|
|||||||
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
|
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
|
||||||
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
<IconWorld className="globe" size={16} strokeWidth={1.5} />
|
||||||
{
|
{
|
||||||
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
|
activeEnvironment ? <div className='text-nowrap truncate max-w-32'>{activeEnvironment?.name}</div> : null
|
||||||
}
|
}
|
||||||
</ToolHint>
|
</ToolHint>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,19 @@ import React, { useEffect, useRef } from 'react';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import Portal from 'components/Portal';
|
import Portal from 'components/Portal';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||||
|
|
||||||
const CreateEnvironment = ({ onClose }) => {
|
const CreateEnvironment = ({ onClose }) => {
|
||||||
|
const globalEnvs = useSelector((state) => state?.globalEnvironments?.globalEnvironments);
|
||||||
|
|
||||||
|
const validateEnvironmentName = (name) => {
|
||||||
|
const trimmedName = name?.toLowerCase().trim();
|
||||||
|
return globalEnvs.every((env) => env?.name?.toLowerCase().trim() !== trimmedName);
|
||||||
|
};
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
@ -17,9 +24,10 @@ const CreateEnvironment = ({ onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be at least 1 character')
|
.min(1, 'Must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'Must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('Name is required')
|
||||||
|
.test('duplicate-name', 'Global Environment already exists', validateEnvironmentName)
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
dispatch(addGlobalEnvironment({ name: values.name }))
|
dispatch(addGlobalEnvironment({ name: values.name }))
|
||||||
|
@ -23,6 +23,10 @@ const StyledWrapper = styled.div`
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-left: solid 2px transparent;
|
border-left: solid 2px transparent;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -8,6 +8,7 @@ import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
|||||||
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
|
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
|
||||||
import ImportEnvironment from '../ImportEnvironment';
|
import ImportEnvironment from '../ImportEnvironment';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import ToolHint from 'components/ToolHint/index';
|
||||||
|
|
||||||
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
|
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
|
||||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||||
@ -112,13 +113,15 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
|
|||||||
{environments &&
|
{environments &&
|
||||||
environments.length &&
|
environments.length &&
|
||||||
environments.map((env) => (
|
environments.map((env) => (
|
||||||
|
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
|
||||||
<div
|
<div
|
||||||
key={env.uid}
|
id={env.uid}
|
||||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle click
|
||||||
>
|
>
|
||||||
<span className="break-all">{env.name}</span>
|
<span className="break-all">{env.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</ToolHint>
|
||||||
))}
|
))}
|
||||||
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
<div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
|
||||||
+ <span>Create</span>
|
+ <span>Create</span>
|
||||||
|
@ -9,7 +9,6 @@ const StyledMarkdownBodyWrapper = styled.div`
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding-top: 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -55,7 +54,7 @@ const StyledMarkdownBodyWrapper = styled.div`
|
|||||||
height: 1px;
|
height: 1px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
background-color: var(--color-border-default);
|
background-color: var(--color-sidebar-collection-item-active-indent-border);
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,12 +79,6 @@ const StyledMarkdownBodyWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.markdown-body {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledMarkdownBodyWrapper;
|
export default StyledMarkdownBodyWrapper;
|
||||||
|
@ -62,7 +62,7 @@ const Modal = ({
|
|||||||
confirmText,
|
confirmText,
|
||||||
cancelText,
|
cancelText,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
handleConfirm,
|
handleConfirm = () => {},
|
||||||
children,
|
children,
|
||||||
confirmDisabled,
|
confirmDisabled,
|
||||||
hideCancel,
|
hideCancel,
|
||||||
@ -103,7 +103,7 @@ const Modal = ({
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeydown);
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
};
|
};
|
||||||
}, [disableEscapeKey, document]);
|
}, [disableEscapeKey, document, handleConfirm]);
|
||||||
|
|
||||||
let classes = 'bruno-modal';
|
let classes = 'bruno-modal';
|
||||||
if (isClosing) {
|
if (isClosing) {
|
||||||
|
@ -13,9 +13,14 @@ const StyledWrapper = styled.div`
|
|||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
pre.CodeMirror-placeholder {
|
||||||
|
color: ${(props) => props.theme.text};
|
||||||
|
padding-left: 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll {
|
.CodeMirror-scroll {
|
||||||
overflow: hidden !important;
|
overflow: visible !important;
|
||||||
${'' /* padding-bottom: 50px !important; */}
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -30,6 +30,7 @@ class MultiLineEditor extends Component {
|
|||||||
lineWrapping: false,
|
lineWrapping: false,
|
||||||
lineNumbers: false,
|
lineNumbers: false,
|
||||||
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
||||||
|
placeholder: this.props.placeholder,
|
||||||
mode: 'brunovariables',
|
mode: 'brunovariables',
|
||||||
brunoVarInfo: {
|
brunoVarInfo: {
|
||||||
variables
|
variables
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
function Portal({ children, wrapperId }) {
|
function Portal({ children }) {
|
||||||
wrapperId = wrapperId || 'bruno-app-body';
|
return createPortal(children, document.body);
|
||||||
|
|
||||||
return createPortal(children, document.getElementById(wrapperId));
|
|
||||||
}
|
}
|
||||||
export default Portal;
|
export default Portal;
|
||||||
|
@ -90,7 +90,10 @@ const General = ({ close }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addCaCertificate = (e) => {
|
const addCaCertificate = (e) => {
|
||||||
formik.setFieldValue('customCaCertificate.filePath', e.target.files[0]?.path);
|
const filePath = window?.ipcRenderer?.getFilePath(e?.target?.files?.[0]);
|
||||||
|
if (filePath) {
|
||||||
|
formik.setFieldValue('customCaCertificate.filePath', filePath);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteCaCertificate = () => {
|
const deleteCaCertificate = () => {
|
||||||
|
@ -20,6 +20,7 @@ import React from 'react';
|
|||||||
* endsWith : ends with
|
* endsWith : ends with
|
||||||
* between : between
|
* between : between
|
||||||
* isEmpty : is empty
|
* isEmpty : is empty
|
||||||
|
* isNotEmpty : is not empty
|
||||||
* isNull : is null
|
* isNull : is null
|
||||||
* isUndefined : is undefined
|
* isUndefined : is undefined
|
||||||
* isDefined : is defined
|
* isDefined : is defined
|
||||||
@ -51,6 +52,7 @@ const AssertionOperator = ({ operator, onChange }) => {
|
|||||||
'endsWith',
|
'endsWith',
|
||||||
'between',
|
'between',
|
||||||
'isEmpty',
|
'isEmpty',
|
||||||
|
'isNotEmpty',
|
||||||
'isNull',
|
'isNull',
|
||||||
'isUndefined',
|
'isUndefined',
|
||||||
'isDefined',
|
'isDefined',
|
||||||
|
@ -24,6 +24,7 @@ import { useTheme } from 'providers/Theme';
|
|||||||
* endsWith : ends with
|
* endsWith : ends with
|
||||||
* between : between
|
* between : between
|
||||||
* isEmpty : is empty
|
* isEmpty : is empty
|
||||||
|
* isNotEmpty : is not empty
|
||||||
* isNull : is null
|
* isNull : is null
|
||||||
* isUndefined : is undefined
|
* isUndefined : is undefined
|
||||||
* isDefined : is defined
|
* isDefined : is defined
|
||||||
@ -61,6 +62,7 @@ const parseAssertionOperator = (str = '') => {
|
|||||||
'endsWith',
|
'endsWith',
|
||||||
'between',
|
'between',
|
||||||
'isEmpty',
|
'isEmpty',
|
||||||
|
'isNotEmpty',
|
||||||
'isNull',
|
'isNull',
|
||||||
'isUndefined',
|
'isUndefined',
|
||||||
'isDefined',
|
'isDefined',
|
||||||
@ -75,6 +77,7 @@ const parseAssertionOperator = (str = '') => {
|
|||||||
|
|
||||||
const unaryOperators = [
|
const unaryOperators = [
|
||||||
'isEmpty',
|
'isEmpty',
|
||||||
|
'isNotEmpty',
|
||||||
'isNull',
|
'isNull',
|
||||||
'isUndefined',
|
'isUndefined',
|
||||||
'isDefined',
|
'isDefined',
|
||||||
@ -87,7 +90,7 @@ const parseAssertionOperator = (str = '') => {
|
|||||||
'isArray'
|
'isArray'
|
||||||
];
|
];
|
||||||
|
|
||||||
const [operator, ...rest] = str.trim().split(' ');
|
const [operator, ...rest] = str.split(' ');
|
||||||
const value = rest.join(' ');
|
const value = rest.join(' ');
|
||||||
|
|
||||||
if (unaryOperators.includes(operator)) {
|
if (unaryOperators.includes(operator)) {
|
||||||
@ -113,6 +116,7 @@ const parseAssertionOperator = (str = '') => {
|
|||||||
const isUnaryOperator = (operator) => {
|
const isUnaryOperator = (operator) => {
|
||||||
const unaryOperators = [
|
const unaryOperators = [
|
||||||
'isEmpty',
|
'isEmpty',
|
||||||
|
'isNotEmpty',
|
||||||
'isNull',
|
'isNull',
|
||||||
'isUndefined',
|
'isUndefined',
|
||||||
'isDefined',
|
'isDefined',
|
||||||
@ -142,19 +146,8 @@ const AssertionRow = ({
|
|||||||
const { operator, value } = parseAssertionOperator(assertion.value);
|
const { operator, value } = parseAssertionOperator(assertion.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={assertion.uid}>
|
<>
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={assertion.name}
|
|
||||||
className="mousetrap"
|
|
||||||
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<AssertionOperator
|
<AssertionOperator
|
||||||
operator={operator}
|
operator={operator}
|
||||||
@ -162,7 +155,7 @@ const AssertionRow = ({
|
|||||||
handleAssertionChange(
|
handleAssertionChange(
|
||||||
{
|
{
|
||||||
target: {
|
target: {
|
||||||
value: `${op} ${value}`
|
value: isUnaryOperator(op) ? op : `${op} ${value}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
assertion,
|
assertion,
|
||||||
@ -178,7 +171,7 @@ const AssertionRow = ({
|
|||||||
theme={storedTheme}
|
theme={storedTheme}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
onChange={(newValue) =>
|
onChange={(newValue) => {
|
||||||
handleAssertionChange(
|
handleAssertionChange(
|
||||||
{
|
{
|
||||||
target: {
|
target: {
|
||||||
@ -189,6 +182,7 @@ const AssertionRow = ({
|
|||||||
'value'
|
'value'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
onRun={handleRun}
|
onRun={handleRun}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
item={item}
|
item={item}
|
||||||
@ -211,7 +205,7 @@ const AssertionRow = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ const Wrapper = styled.div`
|
|||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
font-weight: 600;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|
||||||
thead,
|
thead,
|
||||||
@ -15,24 +16,15 @@ const Wrapper = styled.div`
|
|||||||
color: ${(props) => props.theme.table.thead.color};
|
color: ${(props) => props.theme.table.thead.color};
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(4) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add-assertion {
|
.btn-add-assertion {
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
@ -42,7 +34,8 @@ const Wrapper = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: solid 1px transparent;
|
border: solid 1px transparent;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
background-color: inherit;
|
color: ${(props) => props.theme.table.input.color};
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
@ -6,6 +6,9 @@ import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxS
|
|||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import AssertionRow from './AssertionRow';
|
import AssertionRow from './AssertionRow';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
|
import { moveAssertion } from 'providers/ReduxStore/slices/collections/index';
|
||||||
|
|
||||||
const Assertions = ({ item, collection }) => {
|
const Assertions = ({ item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -57,21 +60,43 @@ const Assertions = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAssertionDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveAssertion({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Expr', accessor: 'expr', width: '30%' },
|
||||||
<td>Expr</td>
|
{ name: 'Operator', accessor: 'operator', width: '120px' },
|
||||||
<td>Operator</td>
|
{ name: 'Value', accessor: 'value', width: '30%' },
|
||||||
<td>Value</td>
|
{ name: '', accessor: '', width: '15%' }
|
||||||
<td></td>
|
]}
|
||||||
</tr>
|
>
|
||||||
</thead>
|
<ReorderTable updateReorderedItem={handleAssertionDrag}>
|
||||||
<tbody>
|
|
||||||
{assertions && assertions.length
|
{assertions && assertions.length
|
||||||
? assertions.map((assertion) => {
|
? assertions.map((assertion) => {
|
||||||
return (
|
return (
|
||||||
|
<tr key={assertion.uid} data-uid={assertion.uid}>
|
||||||
|
<td className='flex relative'>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
value={assertion.name}
|
||||||
|
className="mousetrap"
|
||||||
|
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<AssertionRow
|
<AssertionRow
|
||||||
key={assertion.uid}
|
key={assertion.uid}
|
||||||
assertion={assertion}
|
assertion={assertion}
|
||||||
@ -82,11 +107,12 @@ const Assertions = ({ item, collection }) => {
|
|||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
handleRun={handleRun}
|
handleRun={handleRun}
|
||||||
/>
|
/>
|
||||||
|
</tr>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
||||||
+ Add Assertion
|
+ Add Assertion
|
||||||
</button>
|
</button>
|
||||||
|
@ -70,6 +70,15 @@ const AuthMode = ({ item, collection }) => {
|
|||||||
>
|
>
|
||||||
Digest Auth
|
Digest Auth
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef?.current?.hide();
|
||||||
|
onModeChange('ntlm');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NTLM Auth
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,110 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const NTLMAuth = ({ item, collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const ntlmAuth = item.draft ? get(item, 'draft.request.auth.ntlm', {}) : get(item, 'request.auth.ntlm', {});
|
||||||
|
|
||||||
|
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||||
|
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
|
||||||
|
const handleUsernameChange = (username) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
username: username,
|
||||||
|
password: ntlmAuth.password,
|
||||||
|
domain: ntlmAuth.domain
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = (password) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
username: ntlmAuth.username,
|
||||||
|
password: password,
|
||||||
|
domain: ntlmAuth.domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDomainChange = (domain) => {
|
||||||
|
dispatch(
|
||||||
|
updateAuth({
|
||||||
|
mode: 'ntlm',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
content: {
|
||||||
|
username: ntlmAuth.username,
|
||||||
|
password: ntlmAuth.password,
|
||||||
|
domain: domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Username</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.username || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleUsernameChange(val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Password</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.password || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
item={item}
|
||||||
|
isSecret={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Domain</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={ntlmAuth.domain || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleDomainChange(val)}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NTLMAuth;
|
@ -6,6 +6,8 @@ import BearerAuth from './BearerAuth';
|
|||||||
import BasicAuth from './BasicAuth';
|
import BasicAuth from './BasicAuth';
|
||||||
import DigestAuth from './DigestAuth';
|
import DigestAuth from './DigestAuth';
|
||||||
import WsseAuth from './WsseAuth';
|
import WsseAuth from './WsseAuth';
|
||||||
|
import NTLMAuth from './NTLMAuth';
|
||||||
|
|
||||||
import ApiKeyAuth from './ApiKeyAuth';
|
import ApiKeyAuth from './ApiKeyAuth';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
||||||
@ -31,6 +33,9 @@ const Auth = ({ item, collection }) => {
|
|||||||
case 'digest': {
|
case 'digest': {
|
||||||
return <DigestAuth collection={collection} item={item} />;
|
return <DigestAuth collection={collection} item={item} />;
|
||||||
}
|
}
|
||||||
|
case 'ntlm': {
|
||||||
|
return <NTLMAuth collection={collection} item={item} />;
|
||||||
|
}
|
||||||
case 'oauth2': {
|
case 'oauth2': {
|
||||||
return <OAuth2 collection={collection} item={item} />;
|
return <OAuth2 collection={collection} item={item} />;
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,14 @@ import { useTheme } from 'providers/Theme';
|
|||||||
import {
|
import {
|
||||||
addFormUrlEncodedParam,
|
addFormUrlEncodedParam,
|
||||||
updateFormUrlEncodedParam,
|
updateFormUrlEncodedParam,
|
||||||
deleteFormUrlEncodedParam
|
deleteFormUrlEncodedParam,
|
||||||
|
moveFormUrlEncodedParam
|
||||||
} from 'providers/ReduxStore/slices/collections';
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
import MultiLineEditor from 'components/MultiLineEditor';
|
import MultiLineEditor from 'components/MultiLineEditor';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
|
||||||
const FormUrlEncodedParams = ({ item, collection }) => {
|
const FormUrlEncodedParams = ({ item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -64,22 +67,31 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveFormUrlEncodedParam({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Key', accessor: 'key', width: '40%' },
|
||||||
<td>Key</td>
|
{ name: 'Value', accessor: 'value', width: '46%' },
|
||||||
<td>Value</td>
|
{ name: '', accessor: '', width: '14%' }
|
||||||
<td></td>
|
]}
|
||||||
</tr>
|
>
|
||||||
</thead>
|
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||||
<tbody>
|
|
||||||
{params && params.length
|
{params && params.length
|
||||||
? params.map((param, index) => {
|
? params.map((param, index) => {
|
||||||
return (
|
return (
|
||||||
<tr key={param.uid}>
|
<tr key={param.uid} data-uid={param.uid}>
|
||||||
<td>
|
<td className='flex relative'>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -131,8 +143,8 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
|
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
|
||||||
+ Add Param
|
+ Add Param
|
||||||
</button>
|
</button>
|
||||||
|
@ -19,14 +19,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,15 @@ import { useTheme } from 'providers/Theme';
|
|||||||
import {
|
import {
|
||||||
addMultipartFormParam,
|
addMultipartFormParam,
|
||||||
updateMultipartFormParam,
|
updateMultipartFormParam,
|
||||||
deleteMultipartFormParam
|
deleteMultipartFormParam,
|
||||||
|
moveMultipartFormParam
|
||||||
} from 'providers/ReduxStore/slices/collections';
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
import MultiLineEditor from 'components/MultiLineEditor';
|
import MultiLineEditor from 'components/MultiLineEditor';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import FilePickerEditor from 'components/FilePickerEditor';
|
import FilePickerEditor from 'components/FilePickerEditor';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
|
|
||||||
const MultipartFormParams = ({ item, collection }) => {
|
const MultipartFormParams = ({ item, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -54,6 +57,10 @@ const MultipartFormParams = ({ item, collection }) => {
|
|||||||
param.value = e.target.value;
|
param.value = e.target.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'contentType': {
|
||||||
|
param.contentType = e.target.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'enabled': {
|
case 'enabled': {
|
||||||
param.enabled = e.target.checked;
|
param.enabled = e.target.checked;
|
||||||
break;
|
break;
|
||||||
@ -78,22 +85,32 @@ const MultipartFormParams = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleParamDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveMultipartFormParam({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Key', accessor: 'key', width: '29%' },
|
||||||
<td>Key</td>
|
{ name: 'Value', accessor: 'value', width: '29%' },
|
||||||
<td>Value</td>
|
{ name: 'Content-Type', accessor: 'content-type', width: '28%' },
|
||||||
<td></td>
|
{ name: '', accessor: '', width: '14%' }
|
||||||
</tr>
|
]}
|
||||||
</thead>
|
>
|
||||||
<tbody>
|
<ReorderTable updateReorderedItem={handleParamDrag}>
|
||||||
{params && params.length
|
{params && params.length
|
||||||
? params.map((param, index) => {
|
? params.map((param, index) => {
|
||||||
return (
|
return (
|
||||||
<tr key={param.uid}>
|
<tr key={param.uid} className='w-full' data-uid={param.uid}>
|
||||||
<td>
|
<td className="flex relative">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -145,6 +162,27 @@ const MultipartFormParams = ({ item, collection }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<MultiLineEditor
|
||||||
|
onSave={onSave}
|
||||||
|
theme={storedTheme}
|
||||||
|
placeholder="Auto"
|
||||||
|
value={param.contentType}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleParamChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
param,
|
||||||
|
'contentType'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
@ -163,8 +201,8 @@ const MultipartFormParams = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
<div>
|
<div>
|
||||||
<button className="btn-add-param text-link pr-2 pt-3 mt-2 select-none" onClick={addParam}>
|
<button className="btn-add-param text-link pr-2 pt-3 mt-2 select-none" onClick={addParam}>
|
||||||
+ Add Param
|
+ Add Param
|
||||||
|
@ -103,7 +103,7 @@ const QueryParams = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleParamDrag = ({ updateReorderedItem }) => {
|
const handleQueryParamDrag = ({ updateReorderedItem }) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
moveQueryParam({
|
moveQueryParam({
|
||||||
collectionUid: collection.uid,
|
collectionUid: collection.uid,
|
||||||
@ -117,7 +117,6 @@ const QueryParams = ({ item, collection }) => {
|
|||||||
<StyledWrapper className="w-full flex flex-col absolute">
|
<StyledWrapper className="w-full flex flex-col absolute">
|
||||||
<div className="flex-1 mt-2">
|
<div className="flex-1 mt-2">
|
||||||
<div className="mb-1 title text-xs">Query</div>
|
<div className="mb-1 title text-xs">Query</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
headers={[
|
headers={[
|
||||||
{ name: 'Name', accessor: 'name', width: '31%' },
|
{ name: 'Name', accessor: 'name', width: '31%' },
|
||||||
@ -125,7 +124,7 @@ const QueryParams = ({ item, collection }) => {
|
|||||||
{ name: '', accessor: '', width: '13%' }
|
{ name: '', accessor: '', width: '13%' }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ReorderTable updateReorderedItem={handleParamDrag}>
|
<ReorderTable updateReorderedItem={handleQueryParamDrag}>
|
||||||
{queryParams && queryParams.length
|
{queryParams && queryParams.length
|
||||||
? queryParams.map((param, index) => (
|
? queryParams.map((param, index) => (
|
||||||
<tr key={param.uid} data-uid={param.uid}>
|
<tr key={param.uid} data-uid={param.uid}>
|
||||||
@ -153,7 +152,7 @@ const QueryParams = ({ item, collection }) => {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center justify-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={param.enabled}
|
checked={param.enabled}
|
||||||
@ -241,11 +240,7 @@ const QueryParams = ({ item, collection }) => {
|
|||||||
: null}
|
: null}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{!(pathParams && pathParams.length) ?
|
{!(pathParams && pathParams.length) ? <div className="title pr-2 py-3 mt-2 text-xs"></div> : null}
|
||||||
<div className="title pr-2 py-3 mt-2 text-xs">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -70,7 +70,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
|
|
||||||
const handleGenerateCode = (e) => {
|
const handleGenerateCode = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
|
if (item?.request?.url !== '' || (item.draft?.request?.url !== undefined && item.draft?.request?.url !== '')) {
|
||||||
setGenerateCodeItemModalOpen(true);
|
setGenerateCodeItemModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
toast.error('URL is required');
|
toast.error('URL is required');
|
||||||
|
@ -48,6 +48,7 @@ const RequestBody = ({ item, collection }) => {
|
|||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
item={item}
|
||||||
theme={displayedTheme}
|
theme={displayedTheme}
|
||||||
font={get(preferences, 'font.codeFont', 'default')}
|
font={get(preferences, 'font.codeFont', 'default')}
|
||||||
fontSize={get(preferences, 'font.codeFontSize')}
|
fontSize={get(preferences, 'font.codeFontSize')}
|
||||||
|
@ -19,14 +19,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,14 @@ import cloneDeep from 'lodash/cloneDeep';
|
|||||||
import { IconTrash } from '@tabler/icons';
|
import { IconTrash } from '@tabler/icons';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
|
import { addRequestHeader, updateRequestHeader, deleteRequestHeader, moveRequestHeader } from 'providers/ReduxStore/slices/collections';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||||
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
|
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||||
|
|
||||||
const RequestHeaders = ({ item, collection }) => {
|
const RequestHeaders = ({ item, collection }) => {
|
||||||
@ -63,22 +65,31 @@ const RequestHeaders = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleHeaderDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveRequestHeader({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Key', accessor: 'key', width: '34%' },
|
||||||
<td>Name</td>
|
{ name: 'Value', accessor: 'value', width: '46%' },
|
||||||
<td>Value</td>
|
{ name: '', accessor: '', width: '20%' }
|
||||||
<td></td>
|
]}
|
||||||
</tr>
|
>
|
||||||
</thead>
|
<ReorderTable updateReorderedItem={handleHeaderDrag}>
|
||||||
<tbody>
|
|
||||||
{headers && headers.length
|
{headers && headers.length
|
||||||
? headers.map((header) => {
|
? headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<tr key={header.uid}>
|
<tr key={header.uid} data-uid={header.uid}>
|
||||||
<td>
|
<td className='flex relative'>
|
||||||
<SingleLineEditor
|
<SingleLineEditor
|
||||||
value={header.name}
|
value={header.name}
|
||||||
theme={storedTheme}
|
theme={storedTheme}
|
||||||
@ -140,8 +151,8 @@ const RequestHeaders = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
||||||
+ Add Header
|
+ Add Header
|
||||||
</button>
|
</button>
|
||||||
|
@ -19,14 +19,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +30,8 @@ const Wrapper = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: solid 1px transparent;
|
border: solid 1px transparent;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
background-color: inherit;
|
color: ${(props) => props.theme.table.input.color};
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
|
@ -3,13 +3,15 @@ import cloneDeep from 'lodash/cloneDeep';
|
|||||||
import { IconTrash } from '@tabler/icons';
|
import { IconTrash } from '@tabler/icons';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
|
import { addVar, updateVar, deleteVar, moveVar } from 'providers/ReduxStore/slices/collections';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
import InfoTip from 'components/InfoTip';
|
import InfoTip from 'components/InfoTip';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { variableNameRegex } from 'utils/common/regex';
|
import { variableNameRegex } from 'utils/common/regex';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
|
|
||||||
const VarsTable = ({ item, collection, vars, varType }) => {
|
const VarsTable = ({ item, collection, vars, varType }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -73,35 +75,41 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVarDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveVar({
|
||||||
|
type: varType,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Name', accessor: 'name', width: '40%' },
|
||||||
<td>Name</td>
|
{ name: varType === 'request' ? (
|
||||||
{varType === 'request' ? (
|
|
||||||
<td>
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>Value</span>
|
<span>Value</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
) : (
|
) : (
|
||||||
<td>
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>Expr</span>
|
<span>Expr</span>
|
||||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
), accessor: 'value', width: '46%' },
|
||||||
)}
|
{ name: '', accessor: '', width: '14%' }
|
||||||
<td></td>
|
]}
|
||||||
</tr>
|
>
|
||||||
</thead>
|
<ReorderTable updateReorderedItem={handleVarDrag}>
|
||||||
<tbody>
|
|
||||||
{vars && vars.length
|
{vars && vars.length
|
||||||
? vars.map((_var) => {
|
? vars.map((_var) => {
|
||||||
return (
|
return (
|
||||||
<tr key={_var.uid}>
|
<tr key={_var.uid} data-uid={_var.uid}>
|
||||||
<td>
|
<td className='flex relative'>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -152,8 +160,8 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={handleAddVar}>
|
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={handleAddVar}>
|
||||||
+ Add
|
+ Add
|
||||||
</button>
|
</button>
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.card {
|
||||||
|
background: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||||
|
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||||
|
|
||||||
|
div.hr {
|
||||||
|
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.border-top {
|
||||||
|
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,47 @@
|
|||||||
|
import { IconLoader2, IconFile } from '@tabler/icons';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const RequestIsLoading = ({ item }) => {
|
||||||
|
return <StyledWrapper>
|
||||||
|
<div className='flex flex-col p-4'>
|
||||||
|
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
|
||||||
|
<div>
|
||||||
|
<div className='font-medium flex items-center gap-2 pb-4'>
|
||||||
|
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
|
||||||
|
File Info
|
||||||
|
</div>
|
||||||
|
<div className='hr'/>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-2'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Name:</span>
|
||||||
|
<div>
|
||||||
|
{item?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-1'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Path:</span>
|
||||||
|
<div className='break-all'>
|
||||||
|
{item?.pathname}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-1 pb-4'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Size:</span>
|
||||||
|
<div>
|
||||||
|
{item?.size?.toFixed?.(2)} MB
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='hr'/>
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RequestIsLoading;
|
@ -0,0 +1,19 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.card {
|
||||||
|
background: ${(props) => props.theme.requestTabPanel.card.bg};
|
||||||
|
border: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||||
|
|
||||||
|
div.hr {
|
||||||
|
border-bottom: 1px solid ${(props) => props.theme.requestTabPanel.card.hr};
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.border-top {
|
||||||
|
border-top: 1px solid ${(props) => props.theme.requestTabPanel.card.border};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,89 @@
|
|||||||
|
import { IconLoader2, IconFile } from '@tabler/icons';
|
||||||
|
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const RequestNotLoaded = ({ collection, item }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleLoadRequestViaWorker = () => {
|
||||||
|
!item?.loading && dispatch(loadRequestViaWorker({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLoadRequest = () => {
|
||||||
|
!item?.loading && dispatch(loadRequest({ collectionUid: collection?.uid, pathname: item?.pathname }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StyledWrapper>
|
||||||
|
<div className='flex flex-col p-4'>
|
||||||
|
<div className='card shadow-sm rounded-md p-4 w-[600px]'>
|
||||||
|
<div>
|
||||||
|
<div className='font-medium flex items-center gap-2 pb-4'>
|
||||||
|
<IconFile size={16} strokeWidth={1.5} className="text-gray-400" />
|
||||||
|
File Info
|
||||||
|
</div>
|
||||||
|
<div className='hr'/>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-2'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Name:</span>
|
||||||
|
<div>{item?.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-1'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Path:</span>
|
||||||
|
<div className='break-all'>{item?.pathname}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center mt-1 pb-4'>
|
||||||
|
<span className='w-12 mr-2 text-muted'>Size:</span>
|
||||||
|
<div>{item?.size?.toFixed?.(2)} MB</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!item?.error && (
|
||||||
|
<>
|
||||||
|
<div className='hr'/>
|
||||||
|
<div className='text-muted text-xs mt-4 mb-2'>
|
||||||
|
Due to its large size, this request wasn't loaded automatically.
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-6 mt-4'>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<button
|
||||||
|
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||||
|
onClick={handleLoadRequest}
|
||||||
|
>
|
||||||
|
Load Request
|
||||||
|
</button>
|
||||||
|
<small className='text-muted mt-1'>
|
||||||
|
May cause the app to freeze temporarily while it runs.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<button
|
||||||
|
className={`submit btn btn-sm btn-secondary w-fit h-fit flex flex-row gap-2 ${item?.loading? 'opacity-50 cursor-blocked': ''}`}
|
||||||
|
onClick={handleLoadRequestViaWorker}
|
||||||
|
>
|
||||||
|
Load Request in Background
|
||||||
|
</button>
|
||||||
|
<small className='text-muted mt-1'>
|
||||||
|
Runs in background.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item?.loading && (
|
||||||
|
<>
|
||||||
|
<div className='hr mt-4'/>
|
||||||
|
<div className='flex items-center gap-2 mt-4'>
|
||||||
|
<IconLoader2 className="animate-spin" size={16} strokeWidth={2} />
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RequestNotLoaded;
|
@ -20,10 +20,13 @@ 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 { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { replacePlaceholders } from 'utils/common/variable-replacer';
|
import { replacePlaceholders } from 'utils/common/variable-replacer';
|
||||||
|
import CollectionOverview from 'components/CollectionSettings/Overview';
|
||||||
|
import RequestNotLoaded from './RequestNotLoaded';
|
||||||
|
import RequestIsLoading from './RequestIsLoading';
|
||||||
|
|
||||||
const MIN_LEFT_PANE_WIDTH = 300;
|
const MIN_LEFT_PANE_WIDTH = 300;
|
||||||
const MIN_RIGHT_PANE_WIDTH = 350;
|
const MIN_RIGHT_PANE_WIDTH = 350;
|
||||||
@ -41,13 +44,18 @@ const RequestTabPanel = () => {
|
|||||||
const _collections = useSelector((state) => state.collections.collections);
|
const _collections = useSelector((state) => state.collections.collections);
|
||||||
|
|
||||||
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
|
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
|
||||||
let collections = produce(_collections, draft => {
|
let collections = produce(_collections, (draft) => {
|
||||||
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
|
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
// add selected global env variables to the collection object
|
// add selected global env variables to the collection object
|
||||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
|
||||||
|
globalEnvironments,
|
||||||
|
activeGlobalEnvironmentUid
|
||||||
|
});
|
||||||
|
const globalEnvSecrets = getGlobalEnvironmentVariablesMasked({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||||
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||||
|
collection.globalEnvSecrets = globalEnvSecrets;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -150,6 +158,11 @@ const RequestTabPanel = () => {
|
|||||||
if (focusedTab.type === 'collection-settings') {
|
if (focusedTab.type === 'collection-settings') {
|
||||||
return <CollectionSettings collection={collection} />;
|
return <CollectionSettings collection={collection} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focusedTab.type === 'collection-overview') {
|
||||||
|
return <CollectionOverview collection={collection} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (focusedTab.type === 'folder-settings') {
|
if (focusedTab.type === 'folder-settings') {
|
||||||
const folder = findItemInCollection(collection, focusedTab.folderUid);
|
const folder = findItemInCollection(collection, focusedTab.folderUid);
|
||||||
return <FolderSettings collection={collection} folder={folder} />;
|
return <FolderSettings collection={collection} folder={folder} />;
|
||||||
@ -164,6 +177,14 @@ const RequestTabPanel = () => {
|
|||||||
return <RequestNotFound itemUid={activeTabUid} />;
|
return <RequestNotFound itemUid={activeTabUid} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item?.partial) {
|
||||||
|
return <RequestNotLoaded item={item} collection={collection} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.loading) {
|
||||||
|
return <RequestIsLoading item={item} />
|
||||||
|
}
|
||||||
|
|
||||||
const handleRun = async () => {
|
const handleRun = async () => {
|
||||||
let newItem = JSON.parse(JSON.stringify(item));
|
let newItem = JSON.parse(JSON.stringify(item));
|
||||||
|
|
||||||
|
@ -13,6 +13,14 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'collection-overview': {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
|
<span className="ml-1 leading-6">Collection</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
case 'security-settings': {
|
case 'security-settings': {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -70,7 +70,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
||||||
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
className="flex items-center justify-between tab-container px-1"
|
className="flex items-center justify-between tab-container px-1"
|
||||||
|
@ -20,14 +20,14 @@ const formatResponse = (data, mode, filter) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
return data;
|
return 'null';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.includes('json')) {
|
if (mode.includes('json')) {
|
||||||
let isValidJSON = false;
|
let isValidJSON = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object';
|
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error parsing JSON: ', error.message);
|
console.log('Error parsing JSON: ', error.message);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ const ResponseSave = ({ item }) => {
|
|||||||
const saveResponseToFile = () => {
|
const saveResponseToFile = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('renderer:save-response-to-file', response, item.requestSent.url)
|
.invoke('renderer:save-response-to-file', response, item?.requestSent?.url)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
toast.error(get(err, 'error.message') || 'Something went wrong!');
|
toast.error(get(err, 'error.message') || 'Something went wrong!');
|
||||||
|
@ -43,7 +43,7 @@ const Timeline = ({ request, response }) => {
|
|||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<pre className="line response font-bold">
|
<pre className="line response font-bold">
|
||||||
<span className="arrow">{'<'}</span> {response.status} {response.statusText}
|
<span className="arrow">{'<'}</span> {response.status} - {response.statusText}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
{responseHeaders.map((h) => {
|
{responseHeaders.map((h) => {
|
||||||
|
@ -9,6 +9,7 @@ import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun }
|
|||||||
import slash from 'utils/common/slash';
|
import slash from 'utils/common/slash';
|
||||||
import ResponsePane from './ResponsePane';
|
import ResponsePane from './ResponsePane';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { areItemsLoading } from 'utils/collections';
|
||||||
|
|
||||||
const getRelativePath = (fullPath, pathname) => {
|
const getRelativePath = (fullPath, pathname) => {
|
||||||
// convert to unix style path
|
// convert to unix style path
|
||||||
@ -59,7 +60,7 @@ export default function RunnerResults({ collection }) {
|
|||||||
pathname: info.pathname,
|
pathname: info.pathname,
|
||||||
relativePath: getRelativePath(collection.pathname, info.pathname)
|
relativePath: getRelativePath(collection.pathname, info.pathname)
|
||||||
};
|
};
|
||||||
if (newItem.status !== 'error') {
|
if (newItem.status !== 'error' && newItem.status !== 'skipped') {
|
||||||
if (newItem.testResults) {
|
if (newItem.testResults) {
|
||||||
const failed = newItem.testResults.filter((result) => result.status === 'fail');
|
const failed = newItem.testResults.filter((result) => result.status === 'fail');
|
||||||
newItem.testStatus = failed.length ? 'fail' : 'pass';
|
newItem.testStatus = failed.length ? 'fail' : 'pass';
|
||||||
@ -106,6 +107,8 @@ export default function RunnerResults({ collection }) {
|
|||||||
return (item.status !== 'error' && item.testStatus === 'fail') || item.assertionStatus === 'fail';
|
return (item.status !== 'error' && item.testStatus === 'fail') || item.assertionStatus === 'fail';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let isCollectionLoading = areItemsLoading(collection);
|
||||||
|
|
||||||
if (!items || !items.length) {
|
if (!items || !items.length) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="px-4 pb-4">
|
<StyledWrapper className="px-4 pb-4">
|
||||||
@ -116,7 +119,7 @@ export default function RunnerResults({ collection }) {
|
|||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
||||||
</div>
|
</div>
|
||||||
|
{isCollectionLoading ? <div className='my-1 danger'>Requests in this collection are still loading.</div> : null}
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<label>Delay (in ms)</label>
|
<label>Delay (in ms)</label>
|
||||||
<input
|
<input
|
||||||
@ -163,29 +166,35 @@ export default function RunnerResults({ collection }) {
|
|||||||
<div className="pb-2 font-medium test-summary">
|
<div className="pb-2 font-medium test-summary">
|
||||||
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
|
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
|
||||||
</div>
|
</div>
|
||||||
|
{runnerInfo?.statusText ?
|
||||||
|
<div className="pb-2 font-medium danger">
|
||||||
|
{runnerInfo?.statusText}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
return (
|
return (
|
||||||
<div key={item.uid}>
|
<div key={item.uid}>
|
||||||
<div className="item-path mt-2">
|
<div className="item-path mt-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
<span>
|
||||||
{item.status !== 'error' && item.testStatus === 'pass' ? (
|
{item.status !== 'error' && item.testStatus === 'pass' && item.status !== 'skipped' ? (
|
||||||
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
|
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
|
||||||
) : (
|
) : (
|
||||||
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
|
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`mr-1 ml-2 ${item.status == 'error' || item.testStatus == 'fail' ? 'danger' : ''}`}
|
className={`mr-1 ml-2 ${item.status == 'error' || item.status == 'skipped' || item.testStatus == 'fail' ? 'danger' : ''}`}
|
||||||
>
|
>
|
||||||
{item.relativePath}
|
{item.relativePath}
|
||||||
</span>
|
</span>
|
||||||
{item.status !== 'error' && item.status !== 'completed' ? (
|
{item.status !== 'error' && item.status !== 'skipped' && item.status !== 'completed' ? (
|
||||||
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
|
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
|
||||||
) : item.responseReceived?.status ? (
|
) : item.responseReceived?.status ? (
|
||||||
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
|
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
|
||||||
(<span className="mr-1">{item.responseReceived?.status}</span>
|
<span className="mr-1">{item.responseReceived?.status}</span>
|
||||||
<span>{item.responseReceived?.statusText}</span>)
|
-
|
||||||
|
<span>{item.responseReceived?.statusText}</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}>
|
<span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}>
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
.partial {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: ${(props) => props.theme.colors.text.danger};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,21 @@
|
|||||||
|
import RequestMethod from "../RequestMethod";
|
||||||
|
import { IconLoader2, IconAlertTriangle, IconAlertCircle } from '@tabler/icons';
|
||||||
|
import StyledWrapper from "./StyledWrapper";
|
||||||
|
|
||||||
|
const CollectionItemIcon = ({ item }) => {
|
||||||
|
if (item?.error) {
|
||||||
|
return <StyledWrapper><IconAlertCircle className="w-fit mr-2 error" size={18} strokeWidth={1.5} /></StyledWrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.loading) {
|
||||||
|
return <IconLoader2 className="animate-spin w-fit mr-2" size={18} strokeWidth={1.5} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.partial) {
|
||||||
|
return <StyledWrapper><IconAlertTriangle size={18} className="w-fit mr-2 partial" strokeWidth={1.5} /></StyledWrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RequestMethod item={item} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollectionItemIcon;
|
@ -12,10 +12,15 @@ const DeleteCollectionItem = ({ onClose, item, collection }) => {
|
|||||||
const isFolder = isItemAFolder(item);
|
const isFolder = isItemAFolder(item);
|
||||||
const onConfirm = () => {
|
const onConfirm = () => {
|
||||||
dispatch(deleteItem(item.uid, collection.uid)).then(() => {
|
dispatch(deleteItem(item.uid, collection.uid)).then(() => {
|
||||||
|
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
|
// close all tabs that belong to the folder
|
||||||
|
// including the folder itself and its children
|
||||||
|
const tabUids = [...recursivelyGetAllItemUids(item.items), item.uid]
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
closeTabs({
|
closeTabs({
|
||||||
tabUids: recursivelyGetAllItemUids(item.items)
|
tabUids: tabUids
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -35,6 +35,28 @@ const StyledWrapper = styled.div`
|
|||||||
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flexible-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.flexible-container {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 601px) and (max-width: 1200px) {
|
||||||
|
.flexible-container {
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1201px) {
|
||||||
|
.flexible-container {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -48,7 +48,7 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
return (
|
return (
|
||||||
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
|
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="flex w-full">
|
<div className="flex w-full flexible-container">
|
||||||
<div>
|
<div>
|
||||||
<div className="generate-code-sidebar">
|
<div className="generate-code-sidebar">
|
||||||
{languages &&
|
{languages &&
|
||||||
@ -59,7 +59,26 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
className={
|
className={
|
||||||
language.name === selectedLanguage.name ? 'generate-code-item active' : 'generate-code-item'
|
language.name === selectedLanguage.name ? 'generate-code-item active' : 'generate-code-item'
|
||||||
}
|
}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => setSelectedLanguage(language)}
|
onClick={() => setSelectedLanguage(language)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Tab' || (e.shiftKey && e.key === 'Tab')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const currentIndex = languages.findIndex((lang) => lang.name === selectedLanguage.name);
|
||||||
|
const nextIndex = e.shiftKey
|
||||||
|
? (currentIndex - 1 + languages.length) % languages.length
|
||||||
|
: (currentIndex + 1) % languages.length;
|
||||||
|
setSelectedLanguage(languages[nextIndex]);
|
||||||
|
|
||||||
|
// Explicitly focus on the new active element
|
||||||
|
const nextElement = document.querySelector(`[data-language="${languages[nextIndex].name}"]`);
|
||||||
|
nextElement?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
||||||
|
data-language={language.name}
|
||||||
|
aria-pressed={language.name === selectedLanguage.name}
|
||||||
>
|
>
|
||||||
<span className="capitalize">{language.name}</span>
|
<span className="capitalize">{language.name}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -69,6 +88,7 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
<div className="flex-grow p-4">
|
<div className="flex-grow p-4">
|
||||||
{isValidUrl(finalUrl) ? (
|
{isValidUrl(finalUrl) ? (
|
||||||
<CodeView
|
<CodeView
|
||||||
|
tabIndex={-1}
|
||||||
language={selectedLanguage}
|
language={selectedLanguage}
|
||||||
item={{
|
item={{
|
||||||
...item,
|
...item,
|
||||||
|
@ -6,6 +6,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { isItemAFolder } from 'utils/tabs';
|
import { isItemAFolder } from 'utils/tabs';
|
||||||
import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
|
||||||
const RenameCollectionItem = ({ collection, item, onClose }) => {
|
const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -33,7 +34,8 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
}
|
}
|
||||||
dispatch(renameItem(values.name, item.uid, collection.uid))
|
dispatch(renameItem(values.name, item.uid, collection.uid))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success('Request renamed');
|
isFolder && dispatch(closeTabs({ tabUids: [item.uid] }));
|
||||||
|
toast.success(isFolder ? 'Folder renamed' : 'Request renamed');
|
||||||
onClose();
|
onClose();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -4,6 +4,9 @@ const Wrapper = styled.div`
|
|||||||
.bruno-modal-content {
|
.bruno-modal-content {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
.warning {
|
||||||
|
color: ${(props) => props.theme.colors.text.danger};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
@ -7,6 +7,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs';
|
|||||||
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { flattenItems } from 'utils/collections';
|
import { flattenItems } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { areItemsLoading } from 'utils/collections';
|
||||||
|
|
||||||
const RunCollectionItem = ({ collection, item, onClose }) => {
|
const RunCollectionItem = ({ collection, item, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -32,6 +33,10 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
const flattenedItems = flattenItems(item ? item.items : collection.items);
|
const flattenedItems = flattenItems(item ? item.items : collection.items);
|
||||||
const recursiveRunLength = getRequestsCount(flattenedItems);
|
const recursiveRunLength = getRequestsCount(flattenedItems);
|
||||||
|
|
||||||
|
const isFolderLoading = areItemsLoading(item);
|
||||||
|
console.log(item);
|
||||||
|
console.log(isFolderLoading);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<Modal size="md" title="Collection Runner" hideFooter={true} handleCancel={onClose}>
|
<Modal size="md" title="Collection Runner" hideFooter={true} handleCancel={onClose}>
|
||||||
@ -44,13 +49,12 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
<span className="ml-1 text-xs">({runLength} requests)</span>
|
<span className="ml-1 text-xs">({runLength} requests)</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">This will only run the requests in this folder.</div>
|
<div className="mb-8">This will only run the requests in this folder.</div>
|
||||||
|
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<span className="font-medium">Recursive Run</span>
|
<span className="font-medium">Recursive Run</span>
|
||||||
<span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
|
<span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">This will run all the requests in this folder and all its subfolders.</div>
|
<div className={isFolderLoading ? "mb-2" : "mb-8"}>This will run all the requests in this folder and all its subfolders.</div>
|
||||||
|
{isFolderLoading ? <div className='mb-8 warning'>Requests in this folder are still loading.</div> : null}
|
||||||
<div className="flex justify-end bruno-modal-footer">
|
<div className="flex justify-end bruno-modal-footer">
|
||||||
<span className="mr-3">
|
<span className="mr-3">
|
||||||
<button type="button" onClick={onClose} className="btn btn-md btn-close">
|
<button type="button" onClick={onClose} className="btn btn-md btn-close">
|
||||||
|
@ -6,12 +6,11 @@ import { useDrag, useDrop } from 'react-dnd';
|
|||||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { moveItem, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { moveItem, showInFolder, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
import NewFolder from 'components/Sidebar/NewFolder';
|
import NewFolder from 'components/Sidebar/NewFolder';
|
||||||
import RequestMethod from './RequestMethod';
|
|
||||||
import RenameCollectionItem from './RenameCollectionItem';
|
import RenameCollectionItem from './RenameCollectionItem';
|
||||||
import CloneCollectionItem from './CloneCollectionItem';
|
import CloneCollectionItem from './CloneCollectionItem';
|
||||||
import DeleteCollectionItem from './DeleteCollectionItem';
|
import DeleteCollectionItem from './DeleteCollectionItem';
|
||||||
@ -24,7 +23,7 @@ import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
|||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
import NetworkError from 'components/ResponsePane/NetworkError/index';
|
||||||
import { uuid } from 'utils/common';
|
import CollectionItemIcon from './CollectionItemIcon/index';
|
||||||
|
|
||||||
const CollectionItem = ({ item, collection, searchText }) => {
|
const CollectionItem = ({ item, collection, searchText }) => {
|
||||||
const tabs = useSelector((state) => state.tabs.tabs);
|
const tabs = useSelector((state) => state.tabs.tabs);
|
||||||
@ -39,7 +38,9 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
|
const [newRequestModalOpen, setNewRequestModalOpen] = useState(false);
|
||||||
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
||||||
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
|
const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false);
|
||||||
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);
|
|
||||||
|
const hasSearchText = searchText && searchText?.trim()?.length;
|
||||||
|
const itemIsCollapsed = hasSearchText ? false : item.collapsed;
|
||||||
|
|
||||||
const [{ isDragging }, drag] = useDrag({
|
const [{ isDragging }, drag] = useDrag({
|
||||||
type: `COLLECTION_ITEM_${collection.uid}`,
|
type: `COLLECTION_ITEM_${collection.uid}`,
|
||||||
@ -64,14 +65,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (searchText && searchText.length) {
|
|
||||||
setItemisCollapsed(false);
|
|
||||||
} else {
|
|
||||||
setItemisCollapsed(item.collapsed);
|
|
||||||
}
|
|
||||||
}, [searchText, item]);
|
|
||||||
|
|
||||||
const dropdownTippyRef = useRef();
|
const dropdownTippyRef = useRef();
|
||||||
const MenuIcon = forwardRef((props, ref) => {
|
const MenuIcon = forwardRef((props, ref) => {
|
||||||
return (
|
return (
|
||||||
@ -128,6 +121,13 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: item.uid,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
type: 'folder-settings'
|
||||||
|
})
|
||||||
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
collectionFolderClicked({
|
collectionFolderClicked({
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
@ -136,6 +136,15 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFolderCollapse = () => {
|
||||||
|
dispatch(
|
||||||
|
collectionFolderClicked({
|
||||||
|
itemUid: item.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleRightClick = (event) => {
|
const handleRightClick = (event) => {
|
||||||
const _menuDropdown = dropdownTippyRef.current;
|
const _menuDropdown = dropdownTippyRef.current;
|
||||||
if (_menuDropdown) {
|
if (_menuDropdown) {
|
||||||
@ -183,7 +192,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
const handleGenerateCode = (e) => {
|
const handleGenerateCode = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dropdownTippyRef.current.hide();
|
dropdownTippyRef.current.hide();
|
||||||
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
|
if (item?.request?.url !== '' || (item?.draft?.request?.url !== undefined && item?.draft?.request?.url !== '')) {
|
||||||
setGenerateCodeItemModalOpen(true);
|
setGenerateCodeItemModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
toast.error('URL is required');
|
toast.error('URL is required');
|
||||||
@ -211,6 +220,13 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShowInFolder = () => {
|
||||||
|
dispatch(showInFolder(item.pathname)).catch((error) => {
|
||||||
|
console.error('Error opening the folder', error);
|
||||||
|
toast.error('Error opening the folder');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
||||||
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
||||||
|
|
||||||
@ -260,9 +276,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
<div
|
<div
|
||||||
onClick={handleClick}
|
|
||||||
onContextMenu={handleRightClick}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
className="flex flex-grow items-center h-full overflow-hidden"
|
className="flex flex-grow items-center h-full overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
paddingLeft: 8
|
paddingLeft: 8
|
||||||
@ -275,12 +288,18 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
className={iconClassName}
|
className={iconClassName}
|
||||||
style={{ color: 'rgb(160 160 160)' }}
|
style={{ color: 'rgb(160 160 160)' }}
|
||||||
|
onClick={handleFolderCollapse}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-1 flex items-center overflow-hidden">
|
<div
|
||||||
<RequestMethod item={item} />
|
className="ml-1 flex w-full h-full items-center overflow-hidden"
|
||||||
|
onClick={handleClick}
|
||||||
|
onContextMenu={handleRightClick}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
>
|
||||||
|
<CollectionItemIcon item={item} />
|
||||||
<span className="item-name" title={item.name}>
|
<span className="item-name" title={item.name}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
@ -359,6 +378,15 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
Generate Code
|
Generate Code
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
handleShowInFolder();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show in Folder
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item delete-item"
|
className="dropdown-item delete-item"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
@ -3,10 +3,10 @@ import classnames from 'classnames';
|
|||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import { useDrop } from 'react-dnd';
|
import { useDrop } from 'react-dnd';
|
||||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
|
||||||
import Dropdown from 'components/Dropdown';
|
import Dropdown from 'components/Dropdown';
|
||||||
import { collectionClicked } from 'providers/ReduxStore/slices/collections';
|
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
|
||||||
import { moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { mountCollection, moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
import NewRequest from 'components/Sidebar/NewRequest';
|
import NewRequest from 'components/Sidebar/NewRequest';
|
||||||
@ -15,12 +15,12 @@ import CollectionItem from './CollectionItem';
|
|||||||
import RemoveCollection from './RemoveCollection';
|
import RemoveCollection from './RemoveCollection';
|
||||||
import ExportCollection from './ExportCollection';
|
import ExportCollection from './ExportCollection';
|
||||||
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
||||||
import { isItemAFolder, isItemARequest, transformCollectionToSaveToExportAsFile } from 'utils/collections';
|
import { isItemAFolder, isItemARequest } from 'utils/collections';
|
||||||
import exportCollection from 'utils/collections/export';
|
|
||||||
|
|
||||||
import RenameCollection from './RenameCollection';
|
import RenameCollection from './RenameCollection';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import CloneCollection from './CloneCollection/index';
|
import CloneCollection from './CloneCollection';
|
||||||
|
import { areItemsLoading } from 'utils/collections';
|
||||||
|
|
||||||
const Collection = ({ collection, searchText }) => {
|
const Collection = ({ collection, searchText }) => {
|
||||||
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
||||||
@ -29,8 +29,8 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
|
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
|
||||||
const [showExportCollectionModal, setShowExportCollectionModal] = useState(false);
|
const [showExportCollectionModal, setShowExportCollectionModal] = useState(false);
|
||||||
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
||||||
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const isLoading = areItemsLoading(collection);
|
||||||
|
|
||||||
const menuDropdownTippyRef = useRef();
|
const menuDropdownTippyRef = useRef();
|
||||||
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
||||||
@ -52,20 +52,36 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const hasSearchText = searchText && searchText?.trim()?.length;
|
||||||
if (searchText && searchText.length) {
|
const collectionIsCollapsed = hasSearchText ? false : collection.collapsed;
|
||||||
setCollectionIsCollapsed(false);
|
|
||||||
} else {
|
|
||||||
setCollectionIsCollapsed(collection.collapsed);
|
|
||||||
}
|
|
||||||
}, [searchText, collection]);
|
|
||||||
|
|
||||||
const iconClassName = classnames({
|
const iconClassName = classnames({
|
||||||
'rotate-90': !collectionIsCollapsed
|
'rotate-90': !collectionIsCollapsed
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
dispatch(collectionClicked(collection.uid));
|
// Check if the click came from the chevron icon
|
||||||
|
const isChevronClick = event.target.closest('svg')?.classList.contains('chevron-icon');
|
||||||
|
|
||||||
|
if (collection.mountStatus === 'unmounted') {
|
||||||
|
dispatch(mountCollection({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
collectionPathname: collection.pathname,
|
||||||
|
brunoConfig: collection.brunoConfig
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
dispatch(collapseCollection(collection.uid));
|
||||||
|
|
||||||
|
// Only open collection settings if not clicking the chevron
|
||||||
|
if(!isChevronClick) {
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: uuid(),
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
type: 'collection-settings'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRightClick = (event) => {
|
const handleRightClick = (event) => {
|
||||||
@ -147,12 +163,13 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size={16}
|
size={16}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
className={iconClassName}
|
className={`chevron-icon ${iconClassName}`}
|
||||||
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
||||||
/>
|
/>
|
||||||
<div className="ml-1" id="sidebar-collection-name">
|
<div className="ml-1" id="sidebar-collection-name">
|
||||||
{collection.name}
|
{collection.name}
|
||||||
</div>
|
</div>
|
||||||
|
{isLoading ? <IconLoader2 className="animate-spin mx-1" size={18} strokeWidth={1.5} /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="collection-actions">
|
<div className="collection-actions">
|
||||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||||
|
@ -68,7 +68,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
<Modal size="sm" title="Import Collection" hideFooter={true} handleCancel={onClose}>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h3 className="text-sm">Select the type of your existing collection :</h3>
|
<h3 className="text-sm">Select the type of your existing collection :</h3>
|
||||||
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
|
<div className="mt-4 grid grid-rows-2 grid-flow-col gap-2">
|
||||||
|
@ -184,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.36.0</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.36.1</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,16 +63,16 @@ const Table = ({ minColumnWidth = 1, headers = [], children }) => {
|
|||||||
[activeColumnIndex, columns, minColumnWidth]
|
[activeColumnIndex, columns, minColumnWidth]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseUp = useCallback(() => {
|
|
||||||
setActiveColumnIndex(null);
|
|
||||||
removeListeners();
|
|
||||||
}, [removeListeners]);
|
|
||||||
|
|
||||||
const removeListeners = useCallback(() => {
|
const removeListeners = useCallback(() => {
|
||||||
window.removeEventListener('mousemove', handleMouseMove);
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
window.removeEventListener('mouseup', removeListeners);
|
window.removeEventListener('mouseup', removeListeners);
|
||||||
}, [handleMouseMove]);
|
}, [handleMouseMove]);
|
||||||
|
|
||||||
|
const handleMouseUp = useCallback(() => {
|
||||||
|
setActiveColumnIndex(null);
|
||||||
|
removeListeners?.();
|
||||||
|
}, [removeListeners]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeColumnIndex !== null) {
|
if (activeColumnIndex !== null) {
|
||||||
window.addEventListener('mousemove', handleMouseMove);
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
@ -15,7 +15,7 @@ const KeyValueExplorer = ({ data = [], theme }) => {
|
|||||||
<SecretToggle showSecret={showSecret} onClick={() => setShowSecret(!showSecret)} />
|
<SecretToggle showSecret={showSecret} onClick={() => setShowSecret(!showSecret)} />
|
||||||
<table className="border-collapse">
|
<table className="border-collapse">
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((envVar) => (
|
{data.toSorted((a, b) => a.name.localeCompare(b.name)).map((envVar) => (
|
||||||
<tr key={envVar.name}>
|
<tr key={envVar.name}>
|
||||||
<td className="px-2 py-1">{envVar.name}</td>
|
<td className="px-2 py-1">{envVar.name}</td>
|
||||||
<td className="px-2 py-1">
|
<td className="px-2 py-1">
|
||||||
|
14
packages/bruno-app/src/index.js
Normal file
14
packages/bruno-app/src/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './pages/index';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
if (rootElement) {
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
@ -31,6 +31,7 @@ if (!SERVER_RENDERED) {
|
|||||||
require('codemirror/addon/search/jump-to-line');
|
require('codemirror/addon/search/jump-to-line');
|
||||||
require('codemirror/addon/search/search');
|
require('codemirror/addon/search/search');
|
||||||
require('codemirror/addon/search/searchcursor');
|
require('codemirror/addon/search/searchcursor');
|
||||||
|
require('codemirror/addon/display/placeholder');
|
||||||
require('codemirror/keymap/sublime');
|
require('codemirror/keymap/sublime');
|
||||||
|
|
||||||
require('codemirror-graphql/hint');
|
require('codemirror-graphql/hint');
|
||||||
|
@ -25,31 +25,7 @@ import '@fontsource/inter/900.css';
|
|||||||
import { setupPolyfills } from 'utils/common/setupPolyfills';
|
import { setupPolyfills } from 'utils/common/setupPolyfills';
|
||||||
setupPolyfills();
|
setupPolyfills();
|
||||||
|
|
||||||
function SafeHydrate({ children }) {
|
function Main({ children }) {
|
||||||
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NoSsr({ children }) {
|
|
||||||
const SERVER_RENDERED = typeof window === 'undefined';
|
|
||||||
|
|
||||||
if (SERVER_RENDERED) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
|
||||||
const [domLoaded, setDomLoaded] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDomLoaded(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!domLoaded) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.ipcRenderer) {
|
if (!window.ipcRenderer) {
|
||||||
return (
|
return (
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mx-10 my-10 rounded relative" role="alert">
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 mx-10 my-10 rounded relative" role="alert">
|
||||||
@ -65,23 +41,21 @@ function MyApp({ Component, pageProps }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<SafeHydrate>
|
|
||||||
<NoSsr>
|
|
||||||
<Provider store={ReduxStore}>
|
<Provider store={ReduxStore}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
<HotkeysProvider>
|
<HotkeysProvider>
|
||||||
<Component {...pageProps} />
|
{children}
|
||||||
</HotkeysProvider>
|
</HotkeysProvider>
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
</NoSsr>
|
|
||||||
</SafeHydrate>
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApp;
|
export default Main;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user