mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 21:25:45 +00:00
Merge branch 'main' of github.com:usebruno/bruno into feature/auto-scroll-on-tab-change
This commit is contained in:
66
.coderabbit.yaml
Normal file
66
.coderabbit.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
|
||||
language: 'en-US'
|
||||
early_access: false
|
||||
tone_instructions: 'You are an expert code reviewer in TypeScript, JavaScript, NodeJS, and ElectronJS. You work in an enterprise software developer team, providing concise and clear code review advice. You only elaborate or provide detailed explanations when requested.'
|
||||
|
||||
knowledge_base:
|
||||
opt_out: false
|
||||
code_guidelines:
|
||||
enabled: true
|
||||
filePatterns:
|
||||
- '**/CODING_STANDARDS.md'
|
||||
|
||||
reviews:
|
||||
profile: 'chill'
|
||||
request_changes_workflow: false
|
||||
high_level_summary: true
|
||||
poem: true
|
||||
review_status: true
|
||||
collapse_walkthrough: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
base_branches: ['main', 'release/*']
|
||||
path_instructions:
|
||||
- path: 'tests/**/**.*'
|
||||
instructions: |
|
||||
Review the following e2e test code written using the Playwright test library. Ensure that:
|
||||
- Follow best practices for Playwright code and e2e automation
|
||||
- Try to reduce usage of `page.waitForTimeout();` in code unless absolutely necessary and the locator cannot be found using existing `expect()` playwright calls
|
||||
- Avoid using `page.pause()` in code
|
||||
- Use locator variables for locators
|
||||
- Avoid using test.only
|
||||
- Use multiple assertions
|
||||
- Promote the use of `test.step` as much as possible so the generated reports are easier to read
|
||||
- Ensure that the `fixtures` like the collections are nested inside the `fixtures` folder
|
||||
|
||||
|
||||
|
||||
**Fixture Example***: Here's an example of possible fixture and test pair
|
||||
```
|
||||
.
|
||||
├── fixtures
|
||||
│ └── collection
|
||||
│ ├── base.bru
|
||||
│ ├── bruno.json
|
||||
│ ├── collection.bru
|
||||
│ ├── ws-test-request-with-headers.bru
|
||||
│ ├── ws-test-request-with-subproto.bru
|
||||
│ └── ws-test-request.bru
|
||||
├── connection.spec.ts # <- Depends on the collection in ./fixtures/collection
|
||||
├── headers.spec.ts
|
||||
├── persistence.spec.ts
|
||||
├── variable-interpolation
|
||||
│ ├── fixtures
|
||||
│ │ └── collection
|
||||
│ │ ├── environments
|
||||
│ │ ├── bruno.json
|
||||
│ │ └── ws-interpolation-test.bru
|
||||
│ ├── init-user-data
|
||||
│ └── variable-interpolation.spec.ts # <- Depends on the collection in ./variable-interpolation/fixtures/collection
|
||||
└── subproto.spec.ts
|
||||
```
|
||||
|
||||
chat:
|
||||
auto_reply: true
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,9 +1,10 @@
|
||||
# Description
|
||||
### Description
|
||||
|
||||
<!-- Explain here the changes your PR introduces and text to help us understand the context of this change. -->
|
||||
|
||||
### Contribution Checklist:
|
||||
#### Contribution Checklist:
|
||||
|
||||
- [ ] **I've used AI significantly to create this pull request**
|
||||
- [ ] **The pull request only addresses one issue or adds one feature.**
|
||||
- [ ] **The pull request does not introduce any breaking changes**
|
||||
- [ ] **I have added screenshots or gifs to help explain the change if applicable.**
|
||||
@@ -12,6 +13,6 @@
|
||||
|
||||
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
|
||||
|
||||
### Publishing to New Package Managers
|
||||
#### Publishing to New Package Managers
|
||||
|
||||
Please see [here](../publishing.md) for more information.
|
||||
|
||||
@@ -23,4 +23,5 @@ runs:
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
npm run build:schema-types
|
||||
npm run build:bruno-filestore
|
||||
|
||||
4
.github/workflows/npm-bru-cli.yml
vendored
4
.github/workflows/npm-bru-cli.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: |
|
||||
cd packages/bruno-tests/collection
|
||||
npm install
|
||||
bru run --env Prod --output junit.xml --format junit
|
||||
bru run --env Prod --output junit.xml --format junit --sandbox developer
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: dorny/test-reporter@v2
|
||||
|
||||
6
.github/workflows/ssl-tests.yml
vendored
6
.github/workflows/ssl-tests.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
|
||||
13
.github/workflows/tests.yml
vendored
13
.github/workflows/tests.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -30,6 +30,7 @@ jobs:
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build --workspace=packages/bruno-converters
|
||||
npm run build --workspace=packages/bruno-requests
|
||||
npm run build --workspace=packages/bruno-schema-types
|
||||
npm run build --workspace=packages/bruno-filestore
|
||||
|
||||
- name: Lint Check
|
||||
@@ -66,7 +67,7 @@ jobs:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -83,6 +84,7 @@ jobs:
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build --workspace=packages/bruno-converters
|
||||
npm run build --workspace=packages/bruno-requests
|
||||
npm run build --workspace=packages/bruno-schema-types
|
||||
npm run build --workspace=packages/bruno-filestore
|
||||
|
||||
- name: Run Local Testbench
|
||||
@@ -94,7 +96,7 @@ jobs:
|
||||
run: |
|
||||
cd packages/bruno-tests/collection
|
||||
npm install
|
||||
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit
|
||||
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit --sandbox developer
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
@@ -108,7 +110,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: v22.11.x
|
||||
@@ -134,12 +136,13 @@ jobs:
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
npm run build:schema-types
|
||||
npm run build:bruno-filestore
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
xvfb-run npm run test:e2e
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -51,3 +51,12 @@ bruno.iml
|
||||
|
||||
# Playwright
|
||||
/blob-report/
|
||||
|
||||
# Development plan files
|
||||
*.plan.md
|
||||
|
||||
# packages dist
|
||||
packages/bruno-filestore/dist
|
||||
packages/bruno-requests/dist
|
||||
packages/bruno-schema-types/dist
|
||||
packages/bruno-converters/dist
|
||||
|
||||
78
CODING_STANDARDS.md
Normal file
78
CODING_STANDARDS.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Bruno Coding Standards
|
||||
|
||||
- No diffs unless an actual change is made, the code changes need to be as minimal as possible, avoid making un-necessary whitespace diffs. This is already handled by eslint but make sure you check your code changes before commiting and raising a PR.
|
||||
|
||||
## General Style Rules
|
||||
|
||||
- Use 2 spaces for indentation. No tabs, just spaces – keeps everything neat and uniform.
|
||||
|
||||
- Stick to single quotes for strings. Double quotes are cool elsewhere, but here we go single.
|
||||
|
||||
- Always add semicolons at the end of statements. It's like putting a period at the end of a sentence – clarity matters.
|
||||
|
||||
- JSX is enabled, so feel free to use it where it makes sense.
|
||||
|
||||
## Punctuation and Spacing
|
||||
|
||||
- No trailing commas. Keep it clean, no extra commas hanging around.
|
||||
|
||||
- Always use parentheses around parameters in arrow functions. Even for single params – consistency is key.
|
||||
|
||||
- For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline.
|
||||
|
||||
- No newlines inside function parentheses. Keep 'em tight.
|
||||
|
||||
- Space before and after the arrow in arrow functions. `() => {}` is good.
|
||||
|
||||
- No space between function name and parentheses. `func()` not `func ()`.
|
||||
|
||||
- Semicolons go at the end of the line, not on a new line.
|
||||
|
||||
- No strict max length – write readable code, not cramped lines.
|
||||
|
||||
- Multiple expressions per line in JSX are fine – flexibility is nice.
|
||||
|
||||
Remember, these rules are here to make our codebase harmonious. If something doesn't fit perfectly, let's chat about it. Happy coding! 🚀
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
- Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created.
|
||||
|
||||
- Prioritise high-value tests over maximum coverage. Focus on testing behaviour that is critical, complex, or likely to break—don’t chase coverage numbers for their own sake.
|
||||
|
||||
- Write behaviour-driven tests, not implementation-driven ones. Tests should validate real expected output and observable behaviour, not internal details or mocked-out logic unless absolutely necessary.
|
||||
|
||||
- Minimise mocking unless it meaningfully increases clarity or isolates external dependencies. Prefer real flows where practical; only mock external services, slow systems, or non-deterministic behaviour.
|
||||
|
||||
- Keep tests readable and maintainable. Optimise for clarity over cleverness. Name tests descriptively, keep setup minimal, and avoid unnecessary abstraction.
|
||||
|
||||
- Aim for tests that fail usefully. When a test fails, it should clearly indicate what behaviour broke and why.
|
||||
|
||||
- Cover both the “happy path” and the realistically problematic paths. Validate expected success behaviour, but also validate error handling, edge cases, and degraded-mode behaviour when appropriate.
|
||||
|
||||
- Ensure tests are deterministic and reproducible. No randomness, timing dependencies, or environment-specific assumptions without explicit control.
|
||||
|
||||
- Avoid overfitting tests to current behaviour if future flexibility matters. Only assert what needs to be true, not incidental details.
|
||||
|
||||
- Use consistent patterns and helper utilities where they improve clarity. Prefer shared test utilities over copy-pasted setup code, but only when it actually reduces complexity.
|
||||
|
||||
- Tests should be fast enough to run continuously. Avoid long-running operations unless absolutely necessary; prefer lightweight fixtures and isolated units.
|
||||
|
||||
|
||||
## UI Specific instructions
|
||||
|
||||
### React
|
||||
|
||||
- Use styled component's theme prop to manage CSS colors and not CSS variables when in the context of a styled component or any react component using the styled component
|
||||
- Styled Components are used as wrappers to define both self and children components style, tailwind classes are used specifically for layout based styles.
|
||||
- Styled Component CSS might also change layout but tailwind classes shouldn't define colors.
|
||||
|
||||
## Readability and Abstractions
|
||||
|
||||
- Avoid abstractions unless the exact same code is being used in more than 3 places.
|
||||
- Names for functions need to be concise and descriptive.
|
||||
- Add in JSDoc comments to add more details to the abstractions if needed.
|
||||
- Follow functional programming but just enough to be readable, we don't need to go as deep as ADTs and Monads, we want to keep the code pipeline obvious and easy for everyone to read and contribute to.
|
||||
- Avoid single line abstractions where all that's being done is increasing the call stack with one additional function.
|
||||
- Add in meaningful comments instead of obvious ones where complex code flow is explained properly.
|
||||
@@ -70,6 +70,7 @@ npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
npm run build:schema-types
|
||||
npm run build:bruno-filestore
|
||||
|
||||
# bundle js sandbox libraries
|
||||
|
||||
196
eslint.config.js
196
eslint.config.js
@@ -1,6 +1,6 @@
|
||||
// eslint.config.js
|
||||
const { defineConfig } = require("eslint/config");
|
||||
const globals = require("globals");
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const globals = require('globals');
|
||||
const { fixupPluginRules } = require('@eslint/compat');
|
||||
const eslintPluginDiff = require('eslint-plugin-diff');
|
||||
|
||||
@@ -11,6 +11,16 @@ const runESMImports = async () => {
|
||||
};
|
||||
|
||||
module.exports = runESMImports().then(() => defineConfig([
|
||||
// Global ignores - must be a standalone object with ONLY ignores
|
||||
{
|
||||
ignores: [
|
||||
'**/node_modules/**/*',
|
||||
'**/dist/**/*',
|
||||
'**/*.bru',
|
||||
'packages/bruno-js/src/sandbox/bundle-browser-rollup.js',
|
||||
'packages/bruno-app/public/static/**/*'
|
||||
]
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
'diff': fixupPluginRules(eslintPluginDiff),
|
||||
@@ -34,13 +44,13 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
'packages/bruno-converters/**/*.js',
|
||||
'packages/bruno-electron/**/*.js',
|
||||
'packages/bruno-filestore/**/*.ts',
|
||||
'packages/bruno-schema-types/**/*.ts',
|
||||
'packages/bruno-js/**/*.js',
|
||||
'packages/bruno-lang/**/*.js',
|
||||
'packages/bruno-requests/**/*.ts',
|
||||
'packages/bruno-requests/**/*.js',
|
||||
'packages/bruno-tests/**/*.{js,ts}'
|
||||
],
|
||||
processor: 'diff/diff',
|
||||
rules: {
|
||||
...stylistic.configs.customize({
|
||||
indent: 2,
|
||||
@@ -56,7 +66,7 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
minElements: 2,
|
||||
consistent: true
|
||||
}],
|
||||
'@stylistic/function-paren-newline': ['error', 'never'],
|
||||
'@stylistic/function-paren-newline': ['off'],
|
||||
'@stylistic/array-bracket-spacing': ['error', 'never'],
|
||||
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
|
||||
'@stylistic/function-call-spacing': ['error', 'never'],
|
||||
@@ -64,12 +74,14 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
'@stylistic/padding-line-between-statements': ['off'],
|
||||
'@stylistic/semi-style': ['error', 'last'],
|
||||
'@stylistic/max-len': ['off'],
|
||||
'@stylistic/jsx-one-expression-per-line': ['off']
|
||||
'@stylistic/jsx-one-expression-per-line': ['off'],
|
||||
'@stylistic/max-statements-per-line': ['off'],
|
||||
'@stylistic/no-mixed-operators': ['off']
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-app/**/*.{js,jsx,ts}"],
|
||||
ignores: ["**/*.config.js", "**/public/**/*"],
|
||||
files: ['packages/bruno-app/**/*.{js,jsx,ts}'],
|
||||
ignores: ['**/*.config.js', '**/public/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
@@ -82,114 +94,114 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
jsx: true
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
// It prevents lint errors when using CommonJS exports (module.exports) in Jest mocks.
|
||||
files: ["packages/bruno-app/src/test-utils/mocks/codemirror.js"],
|
||||
files: ['packages/bruno-app/src/test-utils/mocks/codemirror.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
...globals.jest
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-cli/**/*.js"],
|
||||
ignores: ["**/*.config.js"],
|
||||
files: ['packages/bruno-cli/**/*.js'],
|
||||
ignores: ['**/*.config.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest"
|
||||
},
|
||||
ecmaVersion: 'latest'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-common/**/*.ts"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-common/**/*.ts'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parser: require("@typescript-eslint/parser"),
|
||||
parser: require('@typescript-eslint/parser'),
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
project: "./packages/bruno-common/tsconfig.json",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './packages/bruno-common/tsconfig.json'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-converters/**/*.js"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-converters/**/*.js'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-electron/**/*.js"],
|
||||
ignores: ["**/*.config.js", "**/web/**/*"],
|
||||
files: ['packages/bruno-electron/**/*.js'],
|
||||
ignores: ['**/*.config.js', '**/web/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
...globals.jest
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-filestore/**/*.ts"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-filestore/**/*.ts'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parser: require("@typescript-eslint/parser"),
|
||||
parser: require('@typescript-eslint/parser'),
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
project: "./packages/bruno-filestore/tsconfig.json",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './packages/bruno-filestore/tsconfig.json'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-js/**/*.js"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-js/**/*.js'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
@@ -200,65 +212,65 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
typeDetectGlobalObject: false
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-lang/**/*.js"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-lang/**/*.js'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-requests/**/*.ts"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-requests/**/*.ts'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parser: require("@typescript-eslint/parser"),
|
||||
parser: require('@typescript-eslint/parser'),
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
project: "./packages/bruno-requests/tsconfig.json",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './packages/bruno-requests/tsconfig.json'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-requests/**/*.js"],
|
||||
ignores: ["**/*.config.js", "**/dist/**/*"],
|
||||
files: ['packages/bruno-requests/**/*.js'],
|
||||
ignores: ['**/*.config.js', '**/dist/**/*'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
...globals.jest
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"no-undef": "error",
|
||||
},
|
||||
},
|
||||
'no-undef': 'error'
|
||||
}
|
||||
}
|
||||
]));
|
||||
|
||||
494
package-lock.json
generated
494
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"packages/bruno-common",
|
||||
"packages/bruno-converters",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-schema-types",
|
||||
"packages/bruno-query",
|
||||
"packages/bruno-js",
|
||||
"packages/bruno-lang",
|
||||
@@ -3554,6 +3555,16 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@develar/schema-utils/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@develar/schema-utils/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -3906,7 +3917,7 @@
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
@@ -4469,9 +4480,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5067,6 +5078,98 @@
|
||||
"jsep": "^0.4.0||^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lydell/node-pty": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.1.0.tgz",
|
||||
"integrity": "sha512-VDD8LtlMTOrPKWMXUAcB9+LTktzuunqrMwkYR1DMRBkS6LQrCt+0/Ws1o2rMml/n3guePpS7cxhHF7Nm5K4iMw==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"@lydell/node-pty-darwin-arm64": "1.1.0",
|
||||
"@lydell/node-pty-darwin-x64": "1.1.0",
|
||||
"@lydell/node-pty-linux-arm64": "1.1.0",
|
||||
"@lydell/node-pty-linux-x64": "1.1.0",
|
||||
"@lydell/node-pty-win32-arm64": "1.1.0",
|
||||
"@lydell/node-pty-win32-x64": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lydell/node-pty-darwin-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-arm64/-/node-pty-darwin-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-7kFD+owAA61qmhJCtoMbqj3Uvff3YHDiU+4on5F2vQdcMI3MuwGi7dM6MkFG/yuzpw8LF2xULpL71tOPUfxs0w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@lydell/node-pty-darwin-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-darwin-x64/-/node-pty-darwin-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-XZdvqj5FjAMjH8bdp0YfaZjur5DrCIDD1VYiE9EkkYVMDQqRUPHYV3U8BVEQVT9hYfjmpr7dNaELF2KyISWSNA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@lydell/node-pty-linux-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-arm64/-/node-pty-linux-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-yyDBmalCfHpLiQMT2zyLcqL2Fay4Xy7rIs8GH4dqKLnEviMvPGOK7LADVkKAsbsyXBSISL3Lt1m1MtxhPH6ckg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@lydell/node-pty-linux-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-linux-x64/-/node-pty-linux-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-NcNqRTD14QT+vXcEuqSSvmWY+0+WUBn2uRE8EN0zKtDpIEr9d+YiFj16Uqds6QfcLCHfZmC+Ls7YzwTaqDnanA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@lydell/node-pty-win32-arm64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-arm64/-/node-pty-win32-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-JOMbCou+0fA7d/m97faIIfIU0jOv8sn2OR7tI45u3AmldKoKoLP8zHY6SAvDDnI3fccO1R2HeR1doVjpS7HM0w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@lydell/node-pty-win32-x64": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lydell/node-pty-win32-x64/-/node-pty-win32-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-3N56BZ+WDFnUMYRtsrr7Ky2mhWGl9xXcyqR6cexfuCqcz9RNWL+KoXRv/nZylY5dYaXkft4JaR1uVu+roiZDAw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@malept/flatpak-bundler": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz",
|
||||
@@ -5566,6 +5669,13 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@opencollection/types": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.1.0.tgz",
|
||||
"integrity": "sha512-/v64ShE+KyDUAfAlO6Qd5wBwPArd603VC44eife/CdmrtPUSIiFBYcZ9gxAD7LlW99J36wb5IkMpKFDvViINiA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz",
|
||||
@@ -6964,25 +7074,6 @@
|
||||
"@rsbuild/core": "1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@rsbuild/plugin-sass/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rsbuild/plugin-sass/node_modules/postcss": {
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
@@ -8232,12 +8323,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/google-protobuf": {
|
||||
"version": "3.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz",
|
||||
"integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
||||
@@ -8373,9 +8458,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
@@ -8388,15 +8473,6 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash.set": {
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.9.tgz",
|
||||
"integrity": "sha512-KOxyNkZpbaggVmqbpr82N2tDVTx05/3/j0f50Es1prxrWB0XYf9p3QNxqcbWb7P1Q9wlvsUSlCFnwlPCIJ46PQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "12.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||
@@ -8422,6 +8498,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/nanoid": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/nanoid/-/nanoid-2.1.0.tgz",
|
||||
"integrity": "sha512-xdkn/oRTA0GSNPLIKZgHWqDTWZsVrieKomxJBOQUK9YDD+zfSgmwD5t4WJYra5S7XyhTw7tfvwznW+pFexaepQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.15.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz",
|
||||
@@ -8858,6 +8943,10 @@
|
||||
"resolved": "packages/bruno-schema",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@usebruno/schema-types": {
|
||||
"resolved": "packages/bruno-schema-types",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@usebruno/tests": {
|
||||
"resolved": "packages/bruno-tests",
|
||||
"link": true
|
||||
@@ -8866,22 +8955,6 @@
|
||||
"resolved": "packages/bruno-toml",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@usebruno/vm2": {
|
||||
"version": "3.9.19",
|
||||
"resolved": "https://registry.npmjs.org/@usebruno/vm2/-/vm2-3.9.19.tgz",
|
||||
"integrity": "sha512-WIrR9ODN2xkwUEoJb3awhCZO2dTgq8NWoObofAGuzFQOQ27rw96d2GJU/T8OKcygjfJiNei9nuqidyMh81kiug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.7.0",
|
||||
"acorn-walk": "^8.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"vm2": "bin/vm2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@web/rollup-plugin-copy": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@web/rollup-plugin-copy/-/rollup-plugin-copy-0.5.1.tgz",
|
||||
@@ -9152,6 +9225,21 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-fit": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
||||
"integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/xterm": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
@@ -9224,6 +9312,7 @@
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -9257,6 +9346,7 @@
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
@@ -9321,16 +9411,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/amdefine": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-0.0.8.tgz",
|
||||
@@ -11654,7 +11734,7 @@
|
||||
"dependencies": {
|
||||
"env-paths": "^2.2.1",
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"parse-json": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -15183,9 +15263,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/google-protobuf": {
|
||||
"version": "3.21.4",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz",
|
||||
"integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.0.tgz",
|
||||
"integrity": "sha512-b8wmenhUMf2WNL+xIJ/slvD/hEE6V3nRnG86O2bzkBrMweM9gnqZE1dfXlDjibY3aXJXDNbAHepevYyQ7qWKsQ==",
|
||||
"license": "(BSD-3-Clause AND Apache-2.0)"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
@@ -15312,20 +15392,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/grpc-reflection-js": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-reflection-js/-/grpc-reflection-js-0.3.0.tgz",
|
||||
"integrity": "sha512-3lhTlQluPxVgbowCXA3tAZC3RJW+GSOUkguLNYl1QffYRiutUB3RDfPkQFTcrCFJgNiIIxx+iJkr8s3uSp3zWA==",
|
||||
"node_modules/grpc-js-reflection-client": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-js-reflection-client/-/grpc-js-reflection-client-1.3.0.tgz",
|
||||
"integrity": "sha512-eJ5/m1pXpcheSjOGExktU69WPUKnL4Su3IxGJYYYjy3/w19vE8dH7Wi46G5T92bpM0eZWftjiM5HduX8CjPq9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"google-protobuf": "^3.12.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"protobufjs": "^7.2.2"
|
||||
"@types/lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"protobufjs": "^7.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grpc/grpc-js": "^1.0.0"
|
||||
"@grpc/grpc-js": "^1.12.6"
|
||||
}
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
@@ -17804,9 +17882,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -18634,12 +18712,6 @@
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.set": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||
"integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@@ -19436,6 +19508,24 @@
|
||||
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/native-reg": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/native-reg/-/native-reg-1.1.1.tgz",
|
||||
@@ -21264,25 +21354,6 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-node": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.2.1.tgz",
|
||||
@@ -22291,7 +22362,7 @@
|
||||
"config-file-ts": "^0.2.4",
|
||||
"dotenv": "^9.0.2",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"json5": "^2.2.0",
|
||||
"lazy-val": "^1.0.4"
|
||||
},
|
||||
@@ -23758,6 +23829,16 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -26867,6 +26948,8 @@
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "5.65.2",
|
||||
"codemirror-graphql": "2.1.1",
|
||||
@@ -26883,6 +26966,7 @@
|
||||
"graphiql": "3.7.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"hexy": "^0.3.5",
|
||||
"httpsnippet": "^3.0.9",
|
||||
"i18next": "24.1.2",
|
||||
"idb": "^7.0.0",
|
||||
@@ -26893,6 +26977,7 @@
|
||||
"jsonc-parser": "^3.2.1",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"know-your-http-well": "^0.5.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-replace-link": "^1.2.0",
|
||||
@@ -28442,6 +28527,15 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-app/node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-app/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -28449,24 +28543,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/bruno-app/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"packages/bruno-app/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
@@ -28490,6 +28566,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"packages/bruno-app/node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/bruno-app/node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
@@ -28533,7 +28615,6 @@
|
||||
"@usebruno/js": "0.12.0",
|
||||
"@usebruno/lang": "0.12.0",
|
||||
"@usebruno/requests": "^0.1.0",
|
||||
"@usebruno/vm2": "^3.9.13",
|
||||
"aws4-axios": "^3.3.0",
|
||||
"axios": "^1.8.3",
|
||||
"axios-ntlm": "^1.4.2",
|
||||
@@ -28545,7 +28626,7 @@
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.11.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
@@ -30169,7 +30250,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@usebruno/schema": "^0.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"jscodeshift": "^17.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "3.3.8",
|
||||
@@ -30239,24 +30320,6 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"packages/bruno-converters/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"packages/bruno-converters/node_modules/rimraf": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
@@ -30280,6 +30343,7 @@
|
||||
"@aws-sdk/credential-providers": "3.750.0",
|
||||
"@grpc/grpc-js": "^1.13.2",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@lydell/node-pty": "^1.1.0",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/converters": "^0.1.0",
|
||||
"@usebruno/filestore": "^0.1.0",
|
||||
@@ -30288,7 +30352,6 @@
|
||||
"@usebruno/node-machine-id": "^2.0.0",
|
||||
"@usebruno/requests": "^0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
"@usebruno/vm2": "^3.9.13",
|
||||
"about-window": "^1.15.2",
|
||||
"aws4-axios": "^3.3.0",
|
||||
"axios": "^1.8.3",
|
||||
@@ -30310,7 +30373,7 @@
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"is-valid-path": "^0.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"nanoid": "3.3.8",
|
||||
@@ -31550,7 +31613,7 @@
|
||||
"hosted-git-info": "^4.1.0",
|
||||
"is-ci": "^3.0.0",
|
||||
"isbinaryfile": "^5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lazy-val": "^1.0.5",
|
||||
"minimatch": "^5.1.1",
|
||||
"read-config-file": "6.3.2",
|
||||
@@ -31597,7 +31660,7 @@
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"is-ci": "^3.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"source-map-support": "^0.5.19",
|
||||
"stat-mode": "^1.0.0",
|
||||
"temp-file": "^3.4.0"
|
||||
@@ -31728,7 +31791,7 @@
|
||||
"builder-util-runtime": "9.2.4",
|
||||
"fs-extra": "^10.1.0",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
"js-yaml": "^4.1.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -31811,24 +31874,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/bruno-electron/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"packages/bruno-electron/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@@ -31858,20 +31903,27 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/nanoid": "^2.1.0",
|
||||
"@usebruno/lang": "0.12.0",
|
||||
"lodash": "^4.17.21"
|
||||
"ajv": "^8.17.1",
|
||||
"lodash": "^4.17.21",
|
||||
"yaml": "^2.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.22.0",
|
||||
"@babel/preset-typescript": "^7.22.0",
|
||||
"@opencollection/types": "0.1.0",
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@rollup/plugin-typescript": "^9.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^24.1.0",
|
||||
"@usebruno/schema-types": "0.0.1",
|
||||
"babel-jest": "^29.7.0",
|
||||
"jest": "^29.2.0",
|
||||
"nanoid": "3.3.8",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "3.29.5",
|
||||
"rollup-plugin-dts": "^5.0.0",
|
||||
@@ -31880,6 +31932,33 @@
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
},
|
||||
"packages/bruno-filestore/node_modules/@rollup/plugin-typescript": {
|
||||
"version": "12.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz",
|
||||
"integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.14.0||^3.0.0||^4.0.0",
|
||||
"tslib": "*",
|
||||
"typescript": ">=3.7.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
},
|
||||
"tslib": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/bruno-filestore/node_modules/@types/node": {
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
@@ -31960,6 +32039,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/bruno-filestore/node_modules/yaml": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
}
|
||||
},
|
||||
"packages/bruno-graphql-docs": {
|
||||
"name": "@usebruno/graphql-docs",
|
||||
"version": "0.1.0",
|
||||
@@ -32046,9 +32137,6 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"rollup": "3.29.5",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@usebruno/vm2": "^3.9.13"
|
||||
}
|
||||
},
|
||||
"packages/bruno-js/node_modules/axios": {
|
||||
@@ -32062,24 +32150,6 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-js/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"packages/bruno-js/node_modules/xml-formatter": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.5.0.tgz",
|
||||
@@ -32129,7 +32199,8 @@
|
||||
"@types/qs": "^6.9.18",
|
||||
"axios": "^1.9.0",
|
||||
"debug": "^4.4.3",
|
||||
"grpc-reflection-js": "^0.3.0",
|
||||
"google-protobuf": "^4.0.0",
|
||||
"grpc-js-reflection-client": "^1.3.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0",
|
||||
@@ -32247,21 +32318,26 @@
|
||||
"yup": "^0.32.11"
|
||||
}
|
||||
},
|
||||
"packages/bruno-schema/node_modules/nanoid": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"packages/bruno-schema-types": {
|
||||
"name": "@usebruno/schema-types",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-schema-types/node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"packages/bruno-tests": {
|
||||
@@ -32277,7 +32353,7 @@
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"fast-xml-parser": "^5.0.8",
|
||||
"http-proxy": "^1.18.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"packages/bruno-common",
|
||||
"packages/bruno-converters",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-schema-types",
|
||||
"packages/bruno-query",
|
||||
"packages/bruno-js",
|
||||
"packages/bruno-lang",
|
||||
@@ -61,6 +62,7 @@
|
||||
"build:bruno-converters": "npm run build --workspace=packages/bruno-converters",
|
||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||
"build:schema-types": "npm run build --workspace=packages/bruno-schema-types",
|
||||
"build:electron": "node ./scripts/build-electron.js",
|
||||
"build:electron:mac": "./scripts/build-electron.sh mac",
|
||||
"build:electron:win": "./scripts/build-electron.sh win",
|
||||
|
||||
@@ -6,4 +6,4 @@ module.exports = {
|
||||
}]
|
||||
],
|
||||
plugins: ['babel-plugin-styled-components']
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
rootDir: '.',
|
||||
transform: {
|
||||
'^.+\\.[jt]sx?$': 'babel-jest',
|
||||
'^.+\\.[jt]sx?$': 'babel-jest'
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!strip-json-comments|nanoid|xml-formatter)/",
|
||||
'/node_modules/(?!strip-json-comments|nanoid|xml-formatter)/'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||
@@ -22,9 +22,9 @@ module.exports = {
|
||||
testEnvironment: 'jsdom',
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom'],
|
||||
setupFiles: [
|
||||
'<rootDir>/jest.setup.js',
|
||||
'<rootDir>/jest.setup.js'
|
||||
],
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/*.spec.[jt]s?(x)'
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "5.65.2",
|
||||
"codemirror-graphql": "2.1.1",
|
||||
@@ -37,6 +39,7 @@
|
||||
"graphiql": "3.7.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"hexy": "^0.3.5",
|
||||
"httpsnippet": "^3.0.9",
|
||||
"i18next": "24.1.2",
|
||||
"idb": "^7.0.0",
|
||||
@@ -47,6 +50,7 @@
|
||||
"jsonc-parser": "^3.2.1",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"know-your-http-well": "^0.5.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-replace-link": "^1.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const darkTheme = {
|
||||
brand: '#546de5',
|
||||
text: 'rgb(52 52 52)',
|
||||
'brand': '#546de5',
|
||||
'text': 'rgb(52 52 52)',
|
||||
'primary-text': '#ffffff',
|
||||
'primary-theme': '#1e1e1e',
|
||||
'secondary-text': '#929292',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const lightTheme = {
|
||||
brand: '#546de5',
|
||||
text: 'rgb(52 52 52)',
|
||||
'brand': '#546de5',
|
||||
'text': 'rgb(52 52 52)',
|
||||
'primary-text': 'rgb(52 52 52)',
|
||||
'primary-theme': '#ffffff',
|
||||
'secondary-text': '#929292',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AccordionItem, AccordionHeader, AccordionContent } from './styledWrappe
|
||||
|
||||
const AccordionContext = createContext();
|
||||
|
||||
const Accordion = ({ children, defaultIndex }) => {
|
||||
const Accordion = ({ children, defaultIndex, dataTestId }) => {
|
||||
const [openIndex, setOpenIndex] = useState(defaultIndex);
|
||||
|
||||
const toggleItem = (index) => {
|
||||
@@ -13,7 +13,7 @@ const Accordion = ({ children, defaultIndex }) => {
|
||||
|
||||
return (
|
||||
<AccordionContext.Provider value={{ openIndex, toggleItem }}>
|
||||
<div>{children}</div>
|
||||
<div data-testid={dataTestId}>{children}</div>
|
||||
</AccordionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const BrunoSupport = ({ onClose }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
|
||||
<Modal size="sm" title="Support" handleCancel={onClose} hideFooter={true}>
|
||||
<div className="collection-options">
|
||||
<div className="mt-2">
|
||||
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
|
||||
|
||||
@@ -18,6 +18,33 @@ const StyledWrapper = styled.div`
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.CodeMirror-placeholder {
|
||||
color: ${(props) => props.theme.text} !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
text-align: left !important;
|
||||
padding-left: 3px !important;
|
||||
}
|
||||
|
||||
/* Override default lint highlight background when emphasizing the gutter */
|
||||
.CodeMirror-lint-line-error,
|
||||
.CodeMirror-lint-line-warning {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
/* Style line numbers when there's a lint issue */
|
||||
.CodeMirror-lint-line-error .CodeMirror-linenumber {
|
||||
color: #d32f2f !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
|
||||
color: #f57c00 !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Removes the glow outline around the folded json */
|
||||
.CodeMirror-foldmarker {
|
||||
text-shadow: none;
|
||||
@@ -73,41 +100,48 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-string {
|
||||
color: #ce9178 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-number {
|
||||
color: #b5cea8 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-atom {
|
||||
color: #569cd6 !important;
|
||||
.cm-s-default, .cm-s-monokai {
|
||||
span.cm-def {
|
||||
color: ${(props) => props.theme.codemirror.tokens.definition} !important;
|
||||
}
|
||||
span.cm-property {
|
||||
color: ${(props) => props.theme.codemirror.tokens.property} !important;
|
||||
}
|
||||
span.cm-string {
|
||||
color: ${(props) => props.theme.codemirror.tokens.string} !important;
|
||||
}
|
||||
span.cm-number {
|
||||
color: ${(props) => props.theme.codemirror.tokens.number} !important;
|
||||
}
|
||||
span.cm-atom {
|
||||
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
|
||||
}
|
||||
span.cm-variable {
|
||||
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
|
||||
}
|
||||
span.cm-keyword {
|
||||
color: ${(props) => props.theme.codemirror.tokens.keyword} !important;
|
||||
}
|
||||
span.cm-comment {
|
||||
color: ${(props) => props.theme.codemirror.tokens.comment} !important;
|
||||
}
|
||||
span.cm-operator {
|
||||
color: ${(props) => props.theme.codemirror.tokens.operator} !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Variable validation colors */
|
||||
.cm-variable-valid {
|
||||
color: green;
|
||||
color: #5fad89 !important; /* Soft sage */
|
||||
}
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
color: #d17b7b !important; /* Soft coral */
|
||||
}
|
||||
|
||||
.CodeMirror-search-hint {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-property {
|
||||
color: #1f61a0 !important;
|
||||
}
|
||||
|
||||
.cm-s-default span.cm-variable {
|
||||
color: #397d13 !important;
|
||||
}
|
||||
|
||||
|
||||
//matching bracket fix
|
||||
.CodeMirror-matchingbracket {
|
||||
@@ -126,6 +160,31 @@ const StyledWrapper = styled.div`
|
||||
.cm-search-current {
|
||||
background: rgba(255, 193, 7, 0.4);
|
||||
}
|
||||
|
||||
.lint-error-tooltip {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
padding: 8px 12px;
|
||||
max-width: 400px;
|
||||
box-shadow: ${(props) => props.theme.shadow.sm};
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
line-height: 1.5;
|
||||
pointer-events: none;
|
||||
|
||||
.lint-tooltip-message {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.lint-tooltip-message.error {
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
}
|
||||
|
||||
.lint-tooltip-message.warning {
|
||||
color: ${(props) => props.theme.colors.text.warning};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -14,6 +14,8 @@ import * as jsonlint from '@prantlf/jsonlint';
|
||||
import { JSHINT } from 'jshint';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
import { setupLinkAware } from 'utils/codemirror/linkAware';
|
||||
import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors';
|
||||
import CodeMirrorSearch from 'components/CodeMirrorSearch';
|
||||
|
||||
const CodeMirror = require('codemirror');
|
||||
@@ -36,7 +38,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.lintOptions = {
|
||||
esversion: 11,
|
||||
expr: true,
|
||||
asi: true
|
||||
asi: true,
|
||||
highlightLines: true
|
||||
};
|
||||
|
||||
this.state = {
|
||||
@@ -49,19 +52,22 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
const editor = (this.editor = CodeMirror(this._node, {
|
||||
value: this.props.value || '',
|
||||
placeholder: '...',
|
||||
lineNumbers: true,
|
||||
lineWrapping: this.props.enableLineWrapping ?? true,
|
||||
tabSize: TAB_SIZE,
|
||||
mode: this.props.mode || 'application/ld+json',
|
||||
brunoVarInfo: {
|
||||
variables
|
||||
},
|
||||
brunoVarInfo: this.props.enableBrunoVarInfo !== false ? {
|
||||
variables,
|
||||
collection: this.props.collection,
|
||||
item: this.props.item
|
||||
} : false,
|
||||
keyMap: 'sublime',
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
showCursorWhenSelecting: true,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
lint: this.lintOptions,
|
||||
readOnly: this.props.readOnly,
|
||||
scrollbarStyle: 'overlay',
|
||||
@@ -99,7 +105,7 @@ export default class CodeEditor extends React.Component {
|
||||
},
|
||||
'Cmd-H': 'replace',
|
||||
'Ctrl-H': 'replace',
|
||||
Tab: function (cm) {
|
||||
'Tab': function (cm) {
|
||||
cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection()
|
||||
? cm.execCommand('indentMore')
|
||||
: cm.replaceSelection(' ', 'end');
|
||||
@@ -145,7 +151,7 @@ export default class CodeEditor extends React.Component {
|
||||
} else if (this.props.mode == 'application/xml') {
|
||||
var doc = new DOMParser();
|
||||
try {
|
||||
//add header element and remove prefix namespaces for DOMParser
|
||||
// add header element and remove prefix namespaces for DOMParser
|
||||
var dcm = doc.parseFromString(
|
||||
'<a> ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '</a>',
|
||||
'application/xml'
|
||||
@@ -182,7 +188,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
return found;
|
||||
});
|
||||
|
||||
|
||||
if (editor) {
|
||||
editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false);
|
||||
editor.on('change', this._onEdit);
|
||||
@@ -191,7 +197,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.addOverlay();
|
||||
|
||||
const getAllVariablesHandler = () => getAllVariables(this.props.collection, this.props.item);
|
||||
|
||||
|
||||
// Setup AutoComplete Helper for all modes
|
||||
const autoCompleteOptions = {
|
||||
showHintsFor: this.props.showHintsFor,
|
||||
@@ -202,6 +208,11 @@ export default class CodeEditor extends React.Component {
|
||||
editor,
|
||||
autoCompleteOptions
|
||||
);
|
||||
|
||||
setupLinkAware(editor);
|
||||
|
||||
// Setup lint error tooltip on line number hover
|
||||
this.cleanupLintErrorTooltip = setupLintErrorTooltip(editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +238,16 @@ export default class CodeEditor extends React.Component {
|
||||
if (!isEqual(variables, this.variables)) {
|
||||
this.addOverlay();
|
||||
}
|
||||
|
||||
// Update collection and item when they change
|
||||
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
|
||||
if (!isEqual(this.props.collection, this.editor.options.brunoVarInfo.collection)) {
|
||||
this.editor.options.brunoVarInfo.collection = this.props.collection;
|
||||
}
|
||||
if (!isEqual(this.props.item, this.editor.options.brunoVarInfo.item)) {
|
||||
this.editor.options.brunoVarInfo.item = this.props.item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.theme !== prevProps.theme && this.editor) {
|
||||
@@ -254,8 +275,13 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor?._destroyLinkAware?.();
|
||||
this.editor.off('change', this._onEdit);
|
||||
this.editor.off('scroll', this.onScroll);
|
||||
|
||||
// Clean up lint error tooltip
|
||||
this.cleanupLintErrorTooltip?.();
|
||||
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
@@ -290,6 +316,11 @@ export default class CodeEditor extends React.Component {
|
||||
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||
this.variables = variables;
|
||||
|
||||
// Update brunoVarInfo with latest variables
|
||||
if (this.props.enableBrunoVarInfo !== false && this.editor.options.brunoVarInfo) {
|
||||
this.editor.options.brunoVarInfo.variables = variables;
|
||||
}
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode, false, this.props.enableVariableHighlighting);
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
||||
@@ -10,10 +10,10 @@ jest.mock('codemirror', () => {
|
||||
|
||||
const MOCK_THEME = {
|
||||
codemirror: {
|
||||
bg: "#1e1e1e",
|
||||
border: "#333",
|
||||
bg: '#1e1e1e',
|
||||
border: '#333'
|
||||
},
|
||||
textLink: "#007acc",
|
||||
textLink: '#007acc'
|
||||
};
|
||||
|
||||
const setupEditorState = (editor, { value, cursorPosition }) => {
|
||||
@@ -27,8 +27,8 @@ const setupEditorState = (editor, { value, cursorPosition }) => {
|
||||
});
|
||||
|
||||
editor.state = {
|
||||
completionActive: null,
|
||||
}
|
||||
completionActive: null
|
||||
};
|
||||
};
|
||||
|
||||
const setupEditorWithRef = () => {
|
||||
@@ -47,5 +47,5 @@ describe('CodeEditor', () => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it("add CodeEditor related tests here", () => {});
|
||||
});
|
||||
it('add CodeEditor related tests here', () => {});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const StyledWrapper = styled.div`
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 1px 2px;
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
margin: 0 1px;
|
||||
height: 28px;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ const StyledWrapper = styled.div`
|
||||
.searchbar-result-count {
|
||||
min-width: 28px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: #aaa;
|
||||
margin: 0 8px 0 1px;
|
||||
white-space: nowrap;
|
||||
@@ -74,7 +74,7 @@ const StyledWrapper = styled.div`
|
||||
color: inherit;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
padding: 1px 2px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -43,16 +43,16 @@ const ApiKeyAuth = ({ collection }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
!apikeyAuth?.placement &&
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
placement: 'header'
|
||||
}
|
||||
})
|
||||
);
|
||||
!apikeyAuth?.placement
|
||||
&& dispatch(
|
||||
updateCollectionAuth({
|
||||
mode: 'apikey',
|
||||
collectionUid: collection.uid,
|
||||
content: {
|
||||
placement: 'header'
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [apikeyAuth]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
|
||||
.auth-mode-selector {
|
||||
background: transparent;
|
||||
|
||||
@@ -87,7 +87,7 @@ const AuthMode = ({ collection }) => {
|
||||
}}
|
||||
>
|
||||
NTLM Auth
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -9,13 +9,7 @@ import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const NTLMAuth = ({ collection }) => {
|
||||
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
@@ -25,7 +19,6 @@ const NTLMAuth = ({ collection }) => {
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
|
||||
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
updateCollectionAuth({
|
||||
@@ -67,10 +60,7 @@ const NTLMAuth = ({ collection }) => {
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
|
||||
@@ -10,7 +10,7 @@ import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCr
|
||||
import OAuth2Implicit from 'components/RequestPane/Auth/OAuth2/Implicit/index';
|
||||
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
|
||||
|
||||
const GrantTypeComponentMap = ({collection }) => {
|
||||
const GrantTypeComponentMap = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const save = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
|
||||
@@ -13,7 +13,6 @@ import StyledWrapper from './StyledWrapper';
|
||||
import OAuth2 from './OAuth2';
|
||||
import NTLMAuth from './NTLMAuth';
|
||||
|
||||
|
||||
const Auth = ({ collection }) => {
|
||||
const authMode = collection.draft?.root ? get(collection, 'draft.root.request.auth.mode') : get(collection, 'root.request.auth.mode');
|
||||
const dispatch = useDispatch();
|
||||
@@ -36,7 +35,7 @@ const Auth = ({ collection }) => {
|
||||
}
|
||||
case 'ntlm': {
|
||||
return <NTLMAuth collection={collection} />;
|
||||
}
|
||||
}
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} />;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ const ClientCertSettings = ({ collection }) => {
|
||||
domain: Yup.string()
|
||||
.required()
|
||||
.trim()
|
||||
.test('not-empty-after-trim', 'Domain is required', value => value && value.trim().length > 0),
|
||||
.test('not-empty-after-trim', 'Domain is required', (value) => value && value.trim().length > 0),
|
||||
type: Yup.string().required().oneOf(['cert', 'pfx']),
|
||||
certFilePath: Yup.string().when('type', {
|
||||
is: (type) => type == 'cert',
|
||||
@@ -146,30 +146,30 @@ const ClientCertSettings = ({ collection }) => {
|
||||
<StyledWrapper className="w-full h-full">
|
||||
<div className="text-xs mb-4 text-muted">Add client certificates to be used for specific domains.</div>
|
||||
|
||||
<h1 className="font-semibold">Client Certificates</h1>
|
||||
<h1 className="font-medium">Client Certificates</h1>
|
||||
<ul className="mt-4">
|
||||
{!clientCertConfig.length
|
||||
? 'No client certificates added'
|
||||
: clientCertConfig.map((clientCert, index) => (
|
||||
<li key={`client-cert-${index}`} className="flex items-center available-certificates p-2 rounded-lg mb-2">
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div className="flex w-full items-center">
|
||||
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
|
||||
{clientCert.domain}
|
||||
</div>
|
||||
<div className="flex w-full items-center">
|
||||
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
|
||||
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
|
||||
</div>
|
||||
<li key={`client-cert-${index}`} className="flex items-center available-certificates p-2 rounded-lg mb-2">
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div className="flex w-full items-center">
|
||||
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
|
||||
{clientCert.domain}
|
||||
</div>
|
||||
<div className="flex w-full items-center">
|
||||
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
|
||||
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
|
||||
</div>
|
||||
<button onClick={() => handleRemove(index)} className="remove-certificate ml-2">
|
||||
<IconTrash size={18} strokeWidth={1.5} />
|
||||
<IconTrash size={18} strokeWidth={1.5} />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<h1 className="font-semibold mt-8 mb-2">Add Client Certificate</h1>
|
||||
<h1 className="font-medium mt-8 mb-2">Add Client Certificate</h1>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="domain">
|
||||
|
||||
@@ -38,21 +38,21 @@ const Docs = ({ collection }) => {
|
||||
}))
|
||||
);
|
||||
toggleViewMode();
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
dispatch(saveCollectionSettings(collection.uid));
|
||||
toggleViewMode();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="h-full w-full relative flex flex-col">
|
||||
<div className='flex flex-row w-full justify-between items-center mb-4'>
|
||||
<div className='text-lg font-medium flex items-center gap-2'>
|
||||
<div className="flex flex-row w-full justify-between items-center mb-4">
|
||||
<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'>
|
||||
<div className="flex flex-row gap-2 items-center justify-center">
|
||||
{isEditing ? (
|
||||
<>
|
||||
<div className="editing-mode" role="tab" onClick={handleDiscardChanges}>
|
||||
@@ -81,14 +81,13 @@ const Docs = ({ collection }) => {
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
) : (
|
||||
<div className='h-full overflow-auto pl-1'>
|
||||
<div className='h-[1px] min-h-[500px]'>
|
||||
<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={documentationPlaceholder} />
|
||||
}
|
||||
docs?.length > 0
|
||||
? <Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
: <Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={documentationPlaceholder} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -98,7 +97,6 @@ const Docs = ({ collection }) => {
|
||||
|
||||
export default Docs;
|
||||
|
||||
|
||||
const documentationPlaceholder = `
|
||||
Welcome to your collection documentation! This space is designed to help you document your API collection effectively.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -16,7 +16,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
@@ -33,7 +33,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.btn-add-header {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
|
||||
@@ -45,7 +45,8 @@ const Headers = ({ collection }) => {
|
||||
const header = cloneDeep(_header);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
header.name = e.target.value;
|
||||
// Strip newlines from header keys
|
||||
header.name = e.target.value.replace(/[\r\n]/g, '');
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
@@ -122,8 +123,7 @@ const Headers = ({ collection }) => {
|
||||
},
|
||||
header,
|
||||
'name'
|
||||
)
|
||||
}
|
||||
)}
|
||||
autocomplete={headerAutoCompleteList}
|
||||
collection={collection}
|
||||
/>
|
||||
@@ -142,8 +142,7 @@ const Headers = ({ collection }) => {
|
||||
},
|
||||
header,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
)}
|
||||
collection={collection}
|
||||
autocomplete={MimeTypes}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
import { getTotalRequestCountInCollection } from 'utils/collections/';
|
||||
import { IconFolder, IconWorld, IconApi, IconShare } from '@tabler/icons';
|
||||
import { areItemsLoading, getItemsLoadStats } from "utils/collections/index";
|
||||
import { useState } from "react";
|
||||
import ShareCollection from "components/ShareCollection/index";
|
||||
import { IconBox, IconFolder, IconWorld, IconApi, IconShare } from '@tabler/icons';
|
||||
import { areItemsLoading, getItemsLoadStats } from 'utils/collections/index';
|
||||
import { useState } from 'react';
|
||||
import ShareCollection from 'components/ShareCollection/index';
|
||||
|
||||
const Info = ({ collection }) => {
|
||||
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
|
||||
@@ -11,23 +11,23 @@ const Info = ({ collection }) => {
|
||||
const isCollectionLoading = areItemsLoading(collection);
|
||||
const { loading: itemsLoadingCount, total: totalItems } = getItemsLoadStats(collection);
|
||||
const [showShareCollectionModal, toggleShowShareCollectionModal] = useState(false);
|
||||
|
||||
|
||||
const handleToggleShowShareCollectionModal = (value) => (e) => {
|
||||
toggleShowShareCollectionModal(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col h-fit">
|
||||
<div className="rounded-lg py-6">
|
||||
<div className="grid gap-6">
|
||||
<div className="grid gap-5">
|
||||
{/* 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">
|
||||
<div className="font-medium">Location</div>
|
||||
<div className="mt-1 text-muted break-all text-xs">
|
||||
{collection.pathname}
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,8 +39,8 @@ const Info = ({ collection }) => {
|
||||
<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">
|
||||
<div className="font-medium">Environments</div>
|
||||
<div className="mt-1 text-muted text-xs">
|
||||
{collection.environments?.length || 0} environment{collection.environments?.length !== 1 ? 's' : ''} configured
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,10 +52,10 @@ const Info = ({ collection }) => {
|
||||
<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">
|
||||
<div className="font-medium">Requests</div>
|
||||
<div className="mt-1 text-muted text-xs">
|
||||
{
|
||||
isCollectionLoading? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
|
||||
isCollectionLoading ? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,8 +66,8 @@ const Info = ({ collection }) => {
|
||||
<IconShare className="w-5 h-5 text-indigo-500" stroke={1.5} />
|
||||
</div>
|
||||
<div className="ml-4 h-full flex flex-col justify-start">
|
||||
<div className="font-semibold text-sm h-fit my-auto">Share</div>
|
||||
<div className="mt-1 text-sm group-hover:underline text-link">
|
||||
<div className="font-medium h-fit my-auto">Share</div>
|
||||
<div className="group-hover:underline text-link text-xs">
|
||||
Share Collection
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,4 +79,4 @@ const Info = ({ collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Info;
|
||||
export default Info;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { flattenItems } from "utils/collections";
|
||||
import { flattenItems } from 'utils/collections';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isItemARequest, itemIsOpenedInTabs } from 'utils/tabs/index';
|
||||
import { getDefaultRequestPaneTab } from 'utils/collections/index';
|
||||
@@ -12,13 +12,13 @@ const RequestsNotLoaded = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const flattenedItems = flattenItems(collection.items);
|
||||
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
|
||||
const itemsFailedLoading = flattenedItems?.filter((item) => item?.partial && !item?.loading);
|
||||
|
||||
if (!itemsFailedLoading?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleRequestClick = (item) => e => {
|
||||
const handleRequestClick = (item) => (e) => {
|
||||
e.preventDefault();
|
||||
if (isItemARequest(item)) {
|
||||
dispatch(hideHomePage());
|
||||
@@ -39,7 +39,7 @@ const RequestsNotLoaded = ({ collection }) => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full card my-2">
|
||||
@@ -61,7 +61,7 @@ const RequestsNotLoaded = ({ collection }) => {
|
||||
<tbody>
|
||||
{flattenedItems?.map((item, index) => (
|
||||
item?.partial && !item?.loading ? (
|
||||
<tr key={index} className='cursor-pointer' onClick={handleRequestClick(item)}>
|
||||
<tr key={index} className="cursor-pointer" onClick={handleRequestClick(item)}>
|
||||
<td className="py-1.5 px-3">
|
||||
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
|
||||
</td>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
import Docs from "../Docs";
|
||||
import Info from "./Info";
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Docs from '../Docs';
|
||||
import Info from './Info';
|
||||
import { IconBox } from '@tabler/icons';
|
||||
import RequestsNotLoaded from "./RequestsNotLoaded";
|
||||
import RequestsNotLoaded from './RequestsNotLoaded';
|
||||
|
||||
const Overview = ({ collection }) => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="grid grid-cols-5 gap-4 h-full">
|
||||
<div className="grid grid-cols-5 gap-5 h-full">
|
||||
<div className="col-span-2">
|
||||
<div className="text-xl font-semibold flex items-center gap-2">
|
||||
<IconBox size={24} stroke={1.5} />
|
||||
<div className="text-lg font-medium flex items-center gap-2">
|
||||
<IconBox size={20} stroke={1.5} />
|
||||
{collection?.name}
|
||||
</div>
|
||||
<Info collection={collection} />
|
||||
@@ -22,6 +22,6 @@ const Overview = ({ collection }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Overview;
|
||||
export default Overview;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
.settings-label {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
border: 1px solid #ccc;
|
||||
padding: 0.15rem 0.45rem;
|
||||
box-shadow: none;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
transition: border-color ease-in-out 0.1s;
|
||||
border-radius: 3px;
|
||||
background-color: ${(props) => props.theme.modal.input.bg};
|
||||
border: 1px solid ${(props) => props.theme.modal.input.border};
|
||||
|
||||
&:focus {
|
||||
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,121 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { updateCollectionPresets } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { get } from 'lodash';
|
||||
|
||||
const PresetsSettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const initialPresets = { requestType: 'http', requestUrl: '' };
|
||||
|
||||
// Get presets from draft.brunoConfig if it exists, otherwise from brunoConfig
|
||||
const currentPresets = collection.draft?.brunoConfig
|
||||
? get(collection, 'draft.brunoConfig.presets', initialPresets)
|
||||
: get(collection, 'brunoConfig.presets', initialPresets);
|
||||
|
||||
// Helper to update presets config
|
||||
const updatePresets = (updates) => {
|
||||
const updatedPresets = { ...currentPresets, ...updates };
|
||||
dispatch(updateCollectionPresets({
|
||||
collectionUid: collection.uid,
|
||||
presets: updatedPresets
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
|
||||
|
||||
const handleRequestTypeChange = (e) => {
|
||||
updatePresets({ requestType: e.target.value });
|
||||
};
|
||||
|
||||
const handleRequestUrlChange = (e) => {
|
||||
updatePresets({ requestUrl: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="h-full w-full">
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
These presets will be used as the default values for new requests in this collection.
|
||||
</div>
|
||||
<div className="bruno-form">
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label flex items-center" htmlFor="enabled">
|
||||
Request Type
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="http"
|
||||
className="cursor-pointer"
|
||||
type="radio"
|
||||
name="requestType"
|
||||
onChange={handleRequestTypeChange}
|
||||
value="http"
|
||||
checked={(currentPresets.requestType || 'http') === 'http'}
|
||||
/>
|
||||
<label htmlFor="http" className="ml-1 cursor-pointer select-none">
|
||||
HTTP
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="graphql"
|
||||
className="ml-4 cursor-pointer"
|
||||
type="radio"
|
||||
name="requestType"
|
||||
onChange={handleRequestTypeChange}
|
||||
value="graphql"
|
||||
checked={(currentPresets.requestType || 'http') === 'graphql'}
|
||||
/>
|
||||
<label htmlFor="graphql" className="ml-1 cursor-pointer select-none">
|
||||
GraphQL
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="grpc"
|
||||
className="ml-4 cursor-pointer"
|
||||
type="radio"
|
||||
name="requestType"
|
||||
onChange={handleRequestTypeChange}
|
||||
value="grpc"
|
||||
checked={(currentPresets.requestType || 'http') === 'grpc'}
|
||||
/>
|
||||
<label htmlFor="grpc" className="ml-1 cursor-pointer select-none">
|
||||
gRPC
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="requestUrl">
|
||||
Base URL
|
||||
</label>
|
||||
<div className="flex items-center w-full">
|
||||
<div className="flex items-center flex-grow input-container h-full">
|
||||
<input
|
||||
id="request-url"
|
||||
type="text"
|
||||
name="requestUrl"
|
||||
placeholder="Request URL"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleRequestUrlChange}
|
||||
value={currentPresets.requestUrl || ''}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default PresetsSettings;
|
||||
@@ -112,7 +112,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<div className="mb-6" data-testid="protobuf-proto-files-section">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-sm flex items-center" htmlFor="protoFiles">
|
||||
<label className="font-medium flex items-center" htmlFor="protoFiles">
|
||||
Proto Files (
|
||||
{protoFiles.length}
|
||||
)
|
||||
@@ -156,7 +156,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<td colSpan="3" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<IconFile size={24} className="text-gray-400 mb-2" />
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">No proto files added</span>
|
||||
<span className="text-gray-500 dark:text-gray-400">No proto files added</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -169,7 +169,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
|
||||
<div className="flex items-center">
|
||||
<IconFile size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100" data-testid="protobuf-proto-file-name">
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100" data-testid="protobuf-proto-file-name">
|
||||
{getBasename(collection.pathname, file.path)}
|
||||
</span>
|
||||
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
|
||||
@@ -219,7 +219,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<div className="mb-6" data-testid="protobuf-import-paths-section">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-sm flex items-center" htmlFor="importPaths">
|
||||
<label className="font-medium flex items-center" htmlFor="importPaths">
|
||||
Import Paths (
|
||||
{importPaths.length}
|
||||
)
|
||||
@@ -265,7 +265,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<td colSpan="4" className="border border-gray-200 dark:border-gray-700 px-3 py-8 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<IconFolder size={24} className="text-gray-400 mb-2" />
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">No import paths added</span>
|
||||
<span className="text-gray-500 dark:text-gray-400">No import paths added</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -288,7 +288,7 @@ const ProtobufSettings = ({ collection }) => {
|
||||
<td className="border border-gray-200 dark:border-gray-700 px-3 py-2">
|
||||
<div className="flex items-center">
|
||||
<IconFolder size={16} className="text-gray-500 dark:text-gray-400 mr-2" />
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<span className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{getBasename(collection.pathname, importPath.path)}
|
||||
</span>
|
||||
{!isValid && <IconAlertCircle size={12} className="text-red-600 dark:text-red-400 ml-2" />}
|
||||
|
||||
@@ -156,9 +156,9 @@ const ProxySettings = ({ collection }) => {
|
||||
<InfoTip infotipId="request-var">
|
||||
<div>
|
||||
<ul>
|
||||
<li><span style={{width: "50px", display: "inline-block"}}>global</span> - use global proxy config</li>
|
||||
<li><span style={{width: "50px", display: "inline-block"}}>enabled</span> - use collection proxy config</li>
|
||||
<li><span style={{width: "50px", display: "inline-block"}}>disable</span> - disable proxy</li>
|
||||
<li><span style={{ width: '50px', display: 'inline-block' }}>global</span> - use global proxy config</li>
|
||||
<li><span style={{ width: '50px', display: 'inline-block' }}>enabled</span> - use collection proxy config</li>
|
||||
<li><span style={{ width: '50px', display: 'inline-block' }}>disable</span> - disable proxy</li>
|
||||
</ul>
|
||||
</div>
|
||||
</InfoTip>
|
||||
@@ -367,4 +367,4 @@ const ProxySettings = ({ collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ProxySettings;
|
||||
export default ProxySettings;
|
||||
|
||||
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -14,7 +14,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
@@ -31,7 +31,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.btn-add-var {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -114,7 +114,7 @@ const VarsTable = ({ collection, vars, varType }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<MultiLineEditor
|
||||
value={_var.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
@@ -127,8 +127,7 @@ const VarsTable = ({ collection, vars, varType }) => {
|
||||
},
|
||||
_var,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
)}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
|
||||
@@ -4,22 +4,18 @@ import VarsTable from './VarsTable';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import DeprecationWarning from 'components/DeprecationWarning';
|
||||
|
||||
const Vars = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const requestVars = collection.draft?.root ? get(collection, 'draft.root.request.vars.req', []) : get(collection, 'root.request.vars.req', []);
|
||||
const responseVars = collection.draft?.root ? get(collection, 'draft.root.request.vars.res', []) : get(collection, 'root.request.vars.res', []);
|
||||
const handleSave = () => dispatch(saveCollectionSettings(collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<VarsTable collection={collection} vars={requestVars} varType="request" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<VarsTable collection={collection} vars={responseVars} varType="response" />
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
Save
|
||||
|
||||
@@ -9,7 +9,6 @@ import Headers from './Headers';
|
||||
import Auth from './Auth';
|
||||
import Script from './Script';
|
||||
import Test from './Tests';
|
||||
import Presets from './Presets';
|
||||
import Protobuf from './Protobuf';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Vars from './Vars/index';
|
||||
@@ -37,13 +36,9 @@ const CollectionSettings = ({ collection }) => {
|
||||
const activeHeadersCount = headers.filter((header) => header.enabled).length;
|
||||
|
||||
const requestVars = collection.draft?.root ? get(collection, 'draft.root.request.vars.req', []) : get(collection, 'root.request.vars.req', []);
|
||||
const responseVars = collection.draft?.root ? get(collection, 'draft.root.request.vars.res', []) : get(collection, 'root.request.vars.res', []);
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length;
|
||||
const authMode = (collection.draft?.root ? get(collection, 'draft.root.request.auth', {}) : get(collection, 'root.request.auth', {})).mode || 'none';
|
||||
|
||||
const presets = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.presets', []) : get(collection, 'brunoConfig.presets', []);
|
||||
const hasPresets = presets && presets.requestUrl !== '';
|
||||
|
||||
const proxyConfig = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.proxy', {}) : get(collection, 'brunoConfig.proxy', {});
|
||||
const proxyEnabled = proxyConfig.hostname ? true : false;
|
||||
const clientCertConfig = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.clientCertificates.certs', []) : get(collection, 'brunoConfig.clientCertificates.certs', []);
|
||||
@@ -69,9 +64,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'tests': {
|
||||
return <Test collection={collection} />;
|
||||
}
|
||||
case 'presets': {
|
||||
return <Presets collection={collection} />;
|
||||
}
|
||||
case 'proxy': {
|
||||
return <ProxySettings collection={collection} />;
|
||||
}
|
||||
@@ -120,10 +112,6 @@ const CollectionSettings = ({ collection }) => {
|
||||
Tests
|
||||
{hasTests && <StatusDot />}
|
||||
</div>
|
||||
<div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}>
|
||||
Presets
|
||||
{hasPresets && <StatusDot />}
|
||||
</div>
|
||||
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||
Proxy
|
||||
{Object.keys(proxyConfig).length > 0 && proxyEnabled && <StatusDot />}
|
||||
|
||||
@@ -125,7 +125,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
);
|
||||
|
||||
if (!isEmpty(validationErrors)) {
|
||||
toast.error(Object.values(validationErrors).join("\n"));
|
||||
toast.error(Object.values(validationErrors).join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,9 +208,9 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
onClose={onClose}
|
||||
handleCancel={onClose}
|
||||
handleConfirm={onSubmit}
|
||||
customHeader={
|
||||
customHeader={(
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<h2 className="text-sm font-bold">{title}</h2>
|
||||
<h2 className="font-bold">{title}</h2>
|
||||
<div className="ml-auto flex items-center ">
|
||||
<ToggleSwitch
|
||||
className="mr-2"
|
||||
@@ -220,16 +220,16 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
setIsRawMode(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<label className="text-sm font-normal mr-4 normal-case">Edit Raw</label>
|
||||
<label className="font-normal mr-4 normal-case">Edit Raw</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
>
|
||||
<form onSubmit={(e) => e.preventDefault()} className="px-2">
|
||||
{isRawMode ? (
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="block text-sm">Set-Cookie String</label>
|
||||
<label className="block">Set-Cookie String</label>
|
||||
<IconInfoCircle id="cookie-raw-info" size={16} strokeWidth={1.5} className="text-gray-400" />
|
||||
<Tooltip
|
||||
anchorId="cookie-raw-info"
|
||||
@@ -248,7 +248,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm mb-1">
|
||||
<label className="block mb-1">
|
||||
Domain<span className="text-red-600">*</span>{' '}
|
||||
</label>
|
||||
<input
|
||||
@@ -262,11 +262,11 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
disabled={!!cookie}
|
||||
/>
|
||||
{formik.touched.domain && formik.errors.domain && (
|
||||
<div className="text-red-500 text-sm mt-1">{formik.errors.domain}</div>
|
||||
<div className="text-red-500 mt-1">{formik.errors.domain}</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm mb-1">Path</label>
|
||||
<label className="block mb-1">Path</label>
|
||||
<input
|
||||
type="text"
|
||||
name="path"
|
||||
@@ -276,11 +276,11 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
disabled={!!cookie}
|
||||
/>
|
||||
{formik.touched.path && formik.errors.path && (
|
||||
<div className="text-red-500 text-sm mt-1">{formik.errors.path}</div>
|
||||
<div className="text-red-500 mt-1">{formik.errors.path}</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm mb-1">
|
||||
<label className="block mb-1">
|
||||
Key<span className="text-red-600">*</span>{' '}
|
||||
</label>
|
||||
<input
|
||||
@@ -294,12 +294,12 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
disabled={!!cookie}
|
||||
/>
|
||||
{formik.touched.key && formik.errors.key && (
|
||||
<div className="text-red-500 text-sm mt-1">{formik.errors.key}</div>
|
||||
<div className="text-red-500 mt-1">{formik.errors.key}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm mb-1">
|
||||
<label className="block mb-1">
|
||||
Value<span className="text-red-600">*</span>{' '}
|
||||
</label>
|
||||
<input
|
||||
@@ -312,7 +312,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
className="block textbox non-passphrase-input w-full"
|
||||
/>
|
||||
{formik.touched.value && formik.errors.value && (
|
||||
<div className="text-red-500 text-sm mt-1">{formik.errors.value}</div>
|
||||
<div className="text-red-500 mt-1">{formik.errors.value}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,7 +320,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
{/* Date Picker */}
|
||||
<div className="w-full flex items-end">
|
||||
<div>
|
||||
<label className="block text-sm mb-1">Expiration ({moment.tz.guess()})</label>
|
||||
<label className="block mb-1">Expiration ({moment.tz.guess()})</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="expires"
|
||||
@@ -332,7 +332,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
min={moment().format(moment.HTML5_FMT.DATETIME_LOCAL)}
|
||||
/>
|
||||
{formik.touched.expires && formik.errors.expires && (
|
||||
<div className="text-red-500 text-sm mt-1">{formik.errors.expires}</div>
|
||||
<div className="text-red-500 mt-1">{formik.errors.expires}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -346,7 +346,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
onChange={formik.handleChange}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span className="text-sm">Secure</span>
|
||||
<span>Secure</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center">
|
||||
@@ -357,7 +357,7 @@ const ModifyCookieModal = ({ onClose, domain, cookie }) => {
|
||||
onChange={formik.handleChange}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span className="text-sm">HTTP Only</span>
|
||||
<span>HTTP Only</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const ClearDomainCookiesModal = ({ onClose, domain, onClear }) => (
|
||||
<Modal onClose={onClose} handleCancel={onClose} title="Clear Domain Cookies" hideFooter={true}>
|
||||
<div className="flex items-center font-normal">
|
||||
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
||||
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
|
||||
</div>
|
||||
<div className="font-normal mt-4">
|
||||
Are you sure you want to clear all cookies for the domain {domain}?
|
||||
@@ -39,7 +39,7 @@ const DeleteCookieModal = ({ onClose, cookieName, onDelete }) => (
|
||||
<Modal onClose={onClose} handleCancel={onClose} title="Delete Cookie" hideFooter={true}>
|
||||
<div className="flex items-center font-normal">
|
||||
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
||||
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
|
||||
</div>
|
||||
<div className="font-normal mt-4">
|
||||
Are you sure you want to delete the cookie {cookieName}?
|
||||
@@ -72,7 +72,7 @@ const CollectionProperties = ({ onClose }) => {
|
||||
const [searchText, setSearchText] = useState(null);
|
||||
|
||||
const handleAddCookie = (domain) => {
|
||||
if(domain) setCurrentDomain(domain);
|
||||
if (domain) setCurrentDomain(domain);
|
||||
setIsModifyCookieModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ const CollectionProperties = ({ onClose }) => {
|
||||
// No cookies found
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<IconCookieOff size={48} strokeWidth={1.5} className="text-gray-500" />
|
||||
<h2 className="text-lg font-semibold mt-4">No cookies found</h2>
|
||||
<h2 className="text-lg font-medium mt-4">No cookies found</h2>
|
||||
<p className="text-gray-500 mt-2">Add cookies to get started</p>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -175,7 +175,7 @@ const CollectionProperties = ({ onClose }) => {
|
||||
// No search results
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<IconSearch size={48} />
|
||||
<h2 className="text-lg font-semibold mt-4">No search results</h2>
|
||||
<h2 className="text-lg font-medium mt-4">No search results</h2>
|
||||
<p className="text-gray-500 mt-2">Try a different search term</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -219,13 +219,13 @@ const CollectionProperties = ({ onClose }) => {
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="text-left border-b border-gray-200 dark:border-neutral-600 text-gray-700 dark:text-gray-300">
|
||||
<th className="py-2 px-4 font-semibold w-32">Name</th>
|
||||
<th className="py-2 px-4 font-semibold w-52">Value</th>
|
||||
<th className="py-2 px-4 font-semibold">Path</th>
|
||||
<th className="py-2 px-4 font-semibold">Expires</th>
|
||||
<th className="py-2 px-4 font-semibold text-center">Secure</th>
|
||||
<th className="py-2 px-4 font-semibold text-center">HTTP Only</th>
|
||||
<th className="py-2 px-4 font-semibold text-right w-24">Actions</th>
|
||||
<th className="py-2 px-4 font-medium w-32">Name</th>
|
||||
<th className="py-2 px-4 font-medium w-52">Value</th>
|
||||
<th className="py-2 px-4 font-medium">Path</th>
|
||||
<th className="py-2 px-4 font-medium">Expires</th>
|
||||
<th className="py-2 px-4 font-medium text-center">Secure</th>
|
||||
<th className="py-2 px-4 font-medium text-center">HTTP Only</th>
|
||||
<th className="py-2 px-4 font-medium text-right w-24">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.deprecation-warning {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
background: ${(props) => props.theme.deprecationWarning.bg};
|
||||
border: 1px solid ${(props) => props.theme.deprecationWarning.border};
|
||||
border-radius: 6px;
|
||||
|
||||
.warning-icon {
|
||||
color: ${(props) => props.theme.deprecationWarning.icon};
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
color: ${(props) => props.theme.deprecationWarning.text};
|
||||
|
||||
a {
|
||||
color: ${(props) => props.theme.textLink};
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import IconAlertTriangleFilled from '../Icons/IconAlertTriangleFilled';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const DeprecationWarning = ({ featureName, learnMoreUrl }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="deprecation-warning">
|
||||
<IconAlertTriangleFilled className="warning-icon" size={16} />
|
||||
<span className="warning-text">
|
||||
{featureName} will be removed in <strong>v3.0.0</strong>. They are deprecated and will no longer be supported. Learn more in{' '}
|
||||
<a href={learnMoreUrl} target="_blank" rel="noreferrer">this post</a> or contact us at{' '}
|
||||
<a href="mailto:support@usebruno.com">support@usebruno.com</a> with questions.
|
||||
</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeprecationWarning;
|
||||
@@ -22,12 +22,12 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
|
||||
.error-count {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -73,12 +73,12 @@ const StyledWrapper = styled.div`
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -98,8 +98,8 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 16px;
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -121,7 +121,7 @@ const StyledWrapper = styled.div`
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
cursor: pointer;
|
||||
transition: background-color 0.1s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
@@ -149,15 +149,15 @@ const StyledWrapper = styled.div`
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
}
|
||||
|
||||
.error-time {
|
||||
color: ${(props) => props.theme.console.timestampColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IconBug } from '@tabler/icons';
|
||||
import {
|
||||
import {
|
||||
setSelectedError,
|
||||
clearDebugErrors
|
||||
} from 'providers/ReduxStore/slices/logs';
|
||||
@@ -10,10 +10,10 @@ import StyledWrapper from './StyledWrapper';
|
||||
const ErrorRow = ({ error, isSelected, onClick }) => {
|
||||
const formatTime = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 3
|
||||
});
|
||||
@@ -38,18 +38,18 @@ const ErrorRow = ({ error, isSelected, onClick }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={`error-row ${isSelected ? 'selected' : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="error-message" title={error.message}>
|
||||
{getShortMessage(error.message)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="error-location" title={error.filename}>
|
||||
{getLocation(error)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="error-time">
|
||||
{formatTime(error.timestamp)}
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@ const ErrorRow = ({ error, isSelected, onClick }) => {
|
||||
|
||||
const DebugTab = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { debugErrors, selectedError } = useSelector(state => state.logs);
|
||||
const { debugErrors, selectedError } = useSelector((state) => state.logs);
|
||||
|
||||
const handleErrorClick = (error) => {
|
||||
dispatch(setSelectedError(error));
|
||||
@@ -85,7 +85,7 @@ const DebugTab = () => {
|
||||
<div>Location</div>
|
||||
<div className="text-right">Time</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="errors-list">
|
||||
{debugErrors.map((error, index) => (
|
||||
<ErrorRow
|
||||
@@ -103,4 +103,4 @@ const DebugTab = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugTab;
|
||||
export default DebugTab;
|
||||
|
||||
@@ -26,12 +26,12 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
|
||||
.error-time {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
@@ -111,8 +111,8 @@ const StyledWrapper = styled.div`
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -131,15 +131,15 @@ const StyledWrapper = styled.div`
|
||||
gap: 4px;
|
||||
|
||||
label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
word-break: break-all;
|
||||
@@ -167,7 +167,7 @@ const StyledWrapper = styled.div`
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
line-height: 1.4;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
align-self: flex-start;
|
||||
@@ -212,7 +212,7 @@ const StyledWrapper = styled.div`
|
||||
.arguments {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
line-height: 1.5;
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
background: transparent;
|
||||
@@ -225,4 +225,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
import {
|
||||
IconX,
|
||||
IconBug,
|
||||
IconFileText,
|
||||
@@ -15,7 +15,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ErrorInfoTab = ({ error }) => {
|
||||
const { version } = useApp();
|
||||
|
||||
|
||||
const formatTimestamp = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
@@ -23,7 +23,7 @@ const ErrorInfoTab = ({ error }) => {
|
||||
|
||||
const generateGitHubIssueUrl = () => {
|
||||
const title = `Bug: ${error.message.substring(0, 50)}${error.message.length > 50 ? '...' : ''}`;
|
||||
|
||||
|
||||
const body = `## Bug Report
|
||||
|
||||
### Error Details
|
||||
@@ -66,7 +66,7 @@ ${error.args ? error.args.map((arg, index) => {
|
||||
|
||||
const encodedTitle = encodeURIComponent(title);
|
||||
const encodedBody = encodeURIComponent(body);
|
||||
|
||||
|
||||
return `https://github.com/usebruno/bruno/issues/new?template=BLANK_ISSUE&title=${encodedTitle}&body=${encodedBody}`;
|
||||
};
|
||||
|
||||
@@ -84,33 +84,33 @@ ${error.args ? error.args.map((arg, index) => {
|
||||
<label>Message:</label>
|
||||
<span className="error-message-full">{error.message || 'No message available'}</span>
|
||||
</div>
|
||||
|
||||
|
||||
{error.filename && (
|
||||
<div className="info-item">
|
||||
<label>File:</label>
|
||||
<span className="file-path">{error.filename}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{error.lineno && (
|
||||
<div className="info-item">
|
||||
<label>Line:</label>
|
||||
<span>{error.lineno}{error.colno ? `:${error.colno}` : ''}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="info-item">
|
||||
<label>Timestamp:</label>
|
||||
<span>{formatTimestamp(error.timestamp)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="section">
|
||||
<h4>Report Issue</h4>
|
||||
<div className="report-section">
|
||||
<p>Found a bug? Help us improve Bruno by reporting this error on GitHub.</p>
|
||||
<button
|
||||
<button
|
||||
className="report-button"
|
||||
onClick={handleReportIssue}
|
||||
title="Report this error on GitHub"
|
||||
@@ -127,11 +127,11 @@ ${error.args ? error.args.map((arg, index) => {
|
||||
const StackTraceTab = ({ error }) => {
|
||||
const formatStackTrace = (stack) => {
|
||||
if (!stack) return 'Stack trace not available';
|
||||
|
||||
|
||||
return stack
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.join('\n');
|
||||
};
|
||||
|
||||
@@ -152,18 +152,18 @@ const StackTraceTab = ({ error }) => {
|
||||
const ArgumentsTab = ({ error }) => {
|
||||
const formatArguments = (args) => {
|
||||
if (!args || args.length === 0) return 'No arguments available';
|
||||
|
||||
|
||||
try {
|
||||
return args.map((arg, index) => {
|
||||
// Handle special Error object format
|
||||
if (arg && typeof arg === 'object' && arg.__type === 'Error') {
|
||||
return `[${index}]: Error: ${arg.message}\n Name: ${arg.name}\n Stack: ${arg.stack || 'No stack trace'}`;
|
||||
}
|
||||
|
||||
|
||||
if (typeof arg === 'object' && arg !== null) {
|
||||
return `[${index}]: ${JSON.stringify(arg, null, 2)}`;
|
||||
}
|
||||
|
||||
|
||||
return `[${index}]: ${String(arg)}`;
|
||||
}).join('\n\n');
|
||||
} catch (e) {
|
||||
@@ -187,7 +187,7 @@ const ArgumentsTab = ({ error }) => {
|
||||
|
||||
const ErrorDetailsPanel = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { selectedError } = useSelector(state => state.logs);
|
||||
const { selectedError } = useSelector((state) => state.logs);
|
||||
const [activeTab, setActiveTab] = useState('info');
|
||||
|
||||
if (!selectedError) return null;
|
||||
@@ -222,8 +222,8 @@ const ErrorDetailsPanel = () => {
|
||||
<span>Error Details</span>
|
||||
<span className="error-time">({formatTime(selectedError.timestamp)})</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className="close-button"
|
||||
onClick={handleClose}
|
||||
title="Close details panel"
|
||||
@@ -233,23 +233,23 @@ const ErrorDetailsPanel = () => {
|
||||
</div>
|
||||
|
||||
<div className="panel-tabs">
|
||||
<button
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'info' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('info')}
|
||||
>
|
||||
<IconFileText size={14} strokeWidth={1.5} />
|
||||
Info
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'stack' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('stack')}
|
||||
>
|
||||
<IconStack size={14} strokeWidth={1.5} />
|
||||
Stack
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'args' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('args')}
|
||||
>
|
||||
@@ -265,4 +265,4 @@ const ErrorDetailsPanel = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorDetailsPanel;
|
||||
export default ErrorDetailsPanel;
|
||||
|
||||
@@ -22,12 +22,12 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
|
||||
.request-count {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@ const StyledWrapper = styled.div`
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,8 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 16px;
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -107,7 +107,7 @@ const StyledWrapper = styled.div`
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
cursor: pointer;
|
||||
transition: background-color 0.1s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
@@ -127,7 +127,7 @@ const StyledWrapper = styled.div`
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -135,8 +135,8 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
}
|
||||
|
||||
.request-domain {
|
||||
@@ -158,20 +158,20 @@ const StyledWrapper = styled.div`
|
||||
.request-time {
|
||||
color: ${(props) => props.theme.console.timestampColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
}
|
||||
|
||||
.request-duration {
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.request-size {
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.console.buttonHoverBg};
|
||||
@@ -225,7 +225,7 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 12px;
|
||||
background: ${(props) => props.theme.console.dropdownHeaderBg};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
@@ -235,7 +235,7 @@ const StyledWrapper = styled.div`
|
||||
border: none;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
@@ -278,16 +278,16 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.filter-option-label {
|
||||
color: ${(props) => props.theme.console.optionLabelColor};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.filter-option-count {
|
||||
color: ${(props) => props.theme.console.optionCountColor};
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 400;
|
||||
margin-left: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
import {
|
||||
IconFilter,
|
||||
IconChevronDown,
|
||||
IconNetwork,
|
||||
IconNetwork
|
||||
} from '@tabler/icons';
|
||||
import {
|
||||
updateNetworkFilter,
|
||||
toggleAllNetworkFilters,
|
||||
import {
|
||||
updateNetworkFilter,
|
||||
toggleAllNetworkFilters,
|
||||
setSelectedRequest
|
||||
} from 'providers/ReduxStore/slices/logs';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -27,8 +27,8 @@ const MethodBadge = ({ method }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className="method-badge"
|
||||
<span
|
||||
className="method-badge"
|
||||
style={{ backgroundColor: getMethodColor(method) }}
|
||||
>
|
||||
{method?.toUpperCase() || 'GET'}
|
||||
@@ -46,10 +46,10 @@ const StatusBadge = ({ status, statusCode }) => {
|
||||
};
|
||||
|
||||
const displayStatus = statusCode || status;
|
||||
|
||||
|
||||
return (
|
||||
<span
|
||||
className="status-badge"
|
||||
<span
|
||||
className="status-badge"
|
||||
style={{ color: getStatusColor(statusCode) }}
|
||||
>
|
||||
{displayStatus}
|
||||
@@ -61,7 +61,7 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const allFiltersEnabled = Object.values(filters).every(f => f);
|
||||
const allFiltersEnabled = Object.values(filters).every((f) => f);
|
||||
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,7 +77,7 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown" ref={dropdownRef}>
|
||||
<button
|
||||
<button
|
||||
className="filter-dropdown-trigger"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
title="Filter requests by method"
|
||||
@@ -88,21 +88,21 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
</span>
|
||||
<IconChevronDown size={14} strokeWidth={1.5} />
|
||||
</button>
|
||||
|
||||
|
||||
{isOpen && (
|
||||
<div className={`filter-dropdown-menu right`}>
|
||||
<div className="filter-dropdown-menu right">
|
||||
<div className="filter-dropdown-header">
|
||||
<span>Filter by Method</span>
|
||||
<button
|
||||
<button
|
||||
className="filter-toggle-all"
|
||||
onClick={() => onToggleAll(!allFiltersEnabled)}
|
||||
>
|
||||
{allFiltersEnabled ? 'Hide All' : 'Show All'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="filter-dropdown-options">
|
||||
{Object.keys(filters).map(method => (
|
||||
{Object.keys(filters).map((method) => (
|
||||
<label key={method} className="filter-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -126,13 +126,13 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
const RequestRow = ({ request, isSelected, onClick }) => {
|
||||
const { data } = request;
|
||||
const { request: req, response: res, timestamp } = data;
|
||||
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 3
|
||||
});
|
||||
@@ -174,34 +174,34 @@ const RequestRow = ({ request, isSelected, onClick }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={`request-row ${isSelected ? 'selected' : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="request-method">
|
||||
<MethodBadge method={req?.method} />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-status">
|
||||
<StatusBadge status={res?.status} statusCode={res?.statusCode} />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-domain" title={getDomain()}>
|
||||
{getDomain()}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-path" title={getPath()}>
|
||||
{getPath()}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-time">
|
||||
{formatTime(timestamp)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-duration">
|
||||
{formatDuration(res?.duration)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="request-size">
|
||||
{formatSize(res?.size)}
|
||||
</div>
|
||||
@@ -211,17 +211,17 @@ const RequestRow = ({ request, isSelected, onClick }) => {
|
||||
|
||||
const NetworkTab = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { networkFilters, selectedRequest } = useSelector(state => state.logs);
|
||||
const collections = useSelector(state => state.collections.collections);
|
||||
const { networkFilters, selectedRequest } = useSelector((state) => state.logs);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
|
||||
const allRequests = useMemo(() => {
|
||||
const requests = [];
|
||||
|
||||
collections.forEach(collection => {
|
||||
|
||||
collections.forEach((collection) => {
|
||||
if (collection.timeline) {
|
||||
collection.timeline
|
||||
.filter(entry => entry.type === 'request')
|
||||
.forEach(entry => {
|
||||
.filter((entry) => entry.type === 'request')
|
||||
.forEach((entry) => {
|
||||
requests.push({
|
||||
...entry,
|
||||
collectionName: collection.name,
|
||||
@@ -230,12 +230,12 @@ const NetworkTab = () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return requests.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}, [collections]);
|
||||
|
||||
const filteredRequests = useMemo(() => {
|
||||
return allRequests.filter(request => {
|
||||
return allRequests.filter((request) => {
|
||||
const method = request.data?.request?.method?.toUpperCase() || 'GET';
|
||||
return networkFilters[method];
|
||||
});
|
||||
@@ -281,7 +281,7 @@ const NetworkTab = () => {
|
||||
<div className="text-right">Duration</div>
|
||||
<div className="text-right">Size</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="requests-list">
|
||||
{filteredRequests.map((request, index) => (
|
||||
<RequestRow
|
||||
@@ -299,4 +299,4 @@ const NetworkTab = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkTab;
|
||||
export default NetworkTab;
|
||||
|
||||
@@ -26,12 +26,12 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
|
||||
.request-time {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
@@ -111,8 +111,8 @@ const StyledWrapper = styled.div`
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
@@ -131,15 +131,15 @@ const StyledWrapper = styled.div`
|
||||
gap: 2px;
|
||||
|
||||
.label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
word-break: break-all;
|
||||
@@ -160,7 +160,7 @@ const StyledWrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
|
||||
thead {
|
||||
@@ -171,10 +171,10 @@ const StyledWrapper = styled.div`
|
||||
|
||||
td {
|
||||
padding: 8px 12px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
}
|
||||
@@ -209,7 +209,7 @@ const StyledWrapper = styled.div`
|
||||
.header-name,
|
||||
.timeline-phase {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
min-width: 120px;
|
||||
}
|
||||
@@ -234,7 +234,7 @@ const StyledWrapper = styled.div`
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
line-height: 1.4;
|
||||
color: ${(props) => props.theme.console.messageColor};
|
||||
overflow: auto;
|
||||
@@ -249,7 +249,7 @@ const StyledWrapper = styled.div`
|
||||
text-align: center;
|
||||
color: ${(props) => props.theme.console.emptyColor};
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
border: 1px solid ${(props) => props.theme.console.border};
|
||||
border-radius: 4px;
|
||||
@@ -285,7 +285,7 @@ const StyledWrapper = styled.div`
|
||||
|
||||
> div {
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
font-size: 12px !important;
|
||||
font-size: ${(props) => props.theme.font.size.sm} !important;
|
||||
padding: 6px 12px !important;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
@@ -336,7 +336,7 @@ const StyledWrapper = styled.div`
|
||||
|
||||
pre {
|
||||
color: ${(props) => props.theme.console.messageColor} !important;
|
||||
font-size: 11px !important;
|
||||
font-size: ${(props) => props.theme.font.size.xs} !important;
|
||||
line-height: 1.4 !important;
|
||||
padding: 12px !important;
|
||||
}
|
||||
@@ -344,4 +344,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
import {
|
||||
IconX,
|
||||
IconFileText,
|
||||
IconArrowRight,
|
||||
@@ -117,7 +117,7 @@ const ResponseTab = ({ response, request, collection }) => {
|
||||
<div className="response-body-container">
|
||||
{response?.data || response?.dataBuffer ? (
|
||||
<QueryResult
|
||||
item={{ uid: uuid()}}
|
||||
item={{ uid: uuid() }}
|
||||
collection={collection}
|
||||
data={response.data}
|
||||
dataBuffer={response.dataBuffer}
|
||||
@@ -155,8 +155,8 @@ const NetworkTab = ({ response }) => {
|
||||
|
||||
const RequestDetailsPanel = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { selectedRequest } = useSelector(state => state.logs);
|
||||
const collections = useSelector(state => state.collections.collections);
|
||||
const { selectedRequest } = useSelector((state) => state.logs);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
const [activeTab, setActiveTab] = useState('request');
|
||||
|
||||
if (!selectedRequest) return null;
|
||||
@@ -164,7 +164,7 @@ const RequestDetailsPanel = () => {
|
||||
const { data } = selectedRequest;
|
||||
const { request, response } = data;
|
||||
|
||||
const collection = collections.find(c => c.uid === selectedRequest.collectionUid);
|
||||
const collection = collections.find((c) => c.uid === selectedRequest.collectionUid);
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(clearSelectedRequest());
|
||||
@@ -196,8 +196,8 @@ const RequestDetailsPanel = () => {
|
||||
<span>Request Details</span>
|
||||
<span className="request-time">({formatTime(selectedRequest.timestamp)})</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className="close-button"
|
||||
onClick={handleClose}
|
||||
title="Close details panel"
|
||||
@@ -207,23 +207,23 @@ const RequestDetailsPanel = () => {
|
||||
</div>
|
||||
|
||||
<div className="panel-tabs">
|
||||
<button
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'request' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('request')}
|
||||
>
|
||||
<IconArrowRight size={14} strokeWidth={1.5} />
|
||||
Request
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'response' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('response')}
|
||||
>
|
||||
<IconFileText size={14} strokeWidth={1.5} />
|
||||
Response
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className={`tab-button ${activeTab === 'network' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('network')}
|
||||
>
|
||||
@@ -239,4 +239,4 @@ const RequestDetailsPanel = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestDetailsPanel;
|
||||
export default RequestDetailsPanel;
|
||||
|
||||
@@ -37,7 +37,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
@@ -89,12 +89,12 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
|
||||
.log-count {
|
||||
color: ${(props) => props.theme.console.countColor};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.console.buttonHoverBg};
|
||||
@@ -235,7 +235,7 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 12px;
|
||||
background: ${(props) => props.theme.console.dropdownHeaderBg};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
@@ -245,7 +245,7 @@ const StyledWrapper = styled.div`
|
||||
border: none;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
@@ -288,13 +288,13 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.filter-option-label {
|
||||
color: ${(props) => props.theme.console.optionLabelColor};
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.filter-option-count {
|
||||
color: ${(props) => props.theme.console.optionCountColor};
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 400;
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -312,12 +312,12 @@ const StyledWrapper = styled.div`
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ const StyledWrapper = styled.div`
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
@@ -346,7 +346,7 @@ const StyledWrapper = styled.div`
|
||||
gap: 12px;
|
||||
padding: 4px 16px;
|
||||
font-family: ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
line-height: 1.4;
|
||||
border-left: 2px solid transparent;
|
||||
transition: background-color 0.1s ease;
|
||||
@@ -431,13 +431,13 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.log-timestamp {
|
||||
color: ${(props) => props.theme.console.timestampColor};
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
text-transform: uppercase;
|
||||
@@ -465,7 +465,7 @@ const StyledWrapper = styled.div`
|
||||
background: transparent !important;
|
||||
|
||||
.object-key-val {
|
||||
font-size: 12px !important;
|
||||
font-size: ${(props) => props.theme.font.size.sm} !important;
|
||||
}
|
||||
|
||||
.object-key {
|
||||
@@ -517,4 +517,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import { IconTerminal, IconX } from '@tabler/icons';
|
||||
import styled from 'styled-components';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
|
||||
const StyledSessionList = styled.div`
|
||||
.session-list-item {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid ${(props) => props.theme.border || 'rgba(255, 255, 255, 0.05)'};
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.sidebarHover || 'rgba(255, 255, 255, 0.05)'};
|
||||
|
||||
.session-close-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: ${(props) => props.theme.sidebarActive || 'rgba(59, 142, 234, 0.12)'};
|
||||
border-left: 2px solid ${(props) => props.theme.brandColor || '#3b8eea'};
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.session-close-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.textSecondary || '#888'};
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.theme.text};
|
||||
background: ${(props) => props.theme.sidebarHover || 'rgba(255, 255, 255, 0.1)'};
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.session-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.text};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.session-icon {
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.session-path {
|
||||
font-size: 11px;
|
||||
color: ${(props) => props.theme.textSecondary || '#888'};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
const SessionList = ({ sessions, activeSessionId, onSelectSession, onCloseSession }) => {
|
||||
const getSessionDisplayInfo = (session) => {
|
||||
if (session.name) {
|
||||
return { name: session.name };
|
||||
}
|
||||
|
||||
if (session.cwd) {
|
||||
// Normalize path and get the last directory name
|
||||
const normalizedPath = session.cwd.replace(/\\/g, '/').replace(/\/$/, '');
|
||||
const pathParts = normalizedPath.split('/').filter((p) => p);
|
||||
|
||||
if (pathParts.length > 0) {
|
||||
const folderName = pathParts[pathParts.length - 1];
|
||||
return { name: folderName };
|
||||
}
|
||||
|
||||
// If it's root or home directory
|
||||
if (normalizedPath === '' || normalizedPath === '/' || normalizedPath.match(/^[A-Z]:\/?$/)) {
|
||||
return { name: 'Root' };
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use a cool name based on session ID
|
||||
const shortId = session.sessionId.split('_')[1]?.slice(-6) || session.sessionId.slice(-6);
|
||||
return { name: `Terminal ${shortId}` };
|
||||
};
|
||||
|
||||
const getFullPath = (session) => {
|
||||
if (session.cwd) {
|
||||
return session.cwd;
|
||||
}
|
||||
return '~ (Home Directory)';
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledSessionList>
|
||||
{sessions.map((session) => {
|
||||
const { name } = getSessionDisplayInfo(session);
|
||||
return (
|
||||
<ToolHint
|
||||
key={session.sessionId}
|
||||
text={getFullPath(session)}
|
||||
toolhintId={`session-path-${session.sessionId}`}
|
||||
place="bottom-start"
|
||||
delayShow={100}
|
||||
>
|
||||
<div
|
||||
className={`session-list-item ${activeSessionId === session.sessionId ? 'active' : ''}`}
|
||||
onClick={() => onSelectSession(session.sessionId)}
|
||||
>
|
||||
<div className="session-name">
|
||||
<IconTerminal className="session-icon" size={14} />
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
<div
|
||||
className="session-close-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCloseSession(session.sessionId);
|
||||
}}
|
||||
>
|
||||
<IconX size={14} />
|
||||
</div>
|
||||
</div>
|
||||
</ToolHint>
|
||||
);
|
||||
})}
|
||||
</StyledSessionList>
|
||||
);
|
||||
};
|
||||
|
||||
export default SessionList;
|
||||
@@ -0,0 +1,201 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${(props) => props.theme.text};
|
||||
|
||||
.xterm-rows {
|
||||
color: ${(props) => props.theme.text} !important;
|
||||
}
|
||||
|
||||
.terminal-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.terminal-sessions-sidebar {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
border-right: 1px solid ${(props) => props.theme.border || 'rgba(255, 255, 255, 0.08)'};
|
||||
background: ${(props) => props.theme.sidebarBackground || props.theme.background};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.terminal-sessions-header {
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: ${(props) => props.theme.text};
|
||||
border-bottom: 1px solid ${(props) => props.theme.border || 'rgba(255, 255, 255, 0.08)'};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.terminal-sessions-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
/* Custom scrollbar styling - subtle */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-session-item {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid ${(props) => props.theme.border};
|
||||
transition: background 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.sidebarHover || 'rgba(255, 255, 255, 0.05)'};
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: ${(props) => props.theme.sidebarActive || 'rgba(59, 142, 234, 0.15)'};
|
||||
border-left: 3px solid ${(props) => props.theme.brandColor || '#3b8eea'};
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-session-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.text};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.terminal-session-path {
|
||||
font-size: 11px;
|
||||
color: ${(props) => props.theme.textSecondary || '#888'};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.terminal-display-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
|
||||
svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.xterm {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.xterm-viewport {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.xterm-screen {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.xterm-decoration-overview-ruler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for terminal */
|
||||
.xterm-viewport::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.xterm-viewport::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.xterm-viewport::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.xterm-viewport::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
.xterm-helper-textarea {
|
||||
position: absolute !important;
|
||||
left: -9999px !important;
|
||||
top: -9999px !important;
|
||||
}
|
||||
|
||||
/* Selection styling */
|
||||
.xterm .xterm-selection div {
|
||||
background-color: rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* Cursor styling */
|
||||
.xterm .xterm-cursor-layer .xterm-cursor {
|
||||
background-color: #d4d4d4 !important;
|
||||
}
|
||||
|
||||
/* Link styling */
|
||||
.xterm .xterm-decoration-link {
|
||||
text-decoration: underline;
|
||||
color: #3b8eea;
|
||||
}
|
||||
|
||||
.xterm .xterm-decoration-link:hover {
|
||||
color: #5ba7f7;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,449 @@
|
||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { IconTerminal2, IconPlus } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SessionList from './SessionList';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
||||
// Terminal instances per session - Map<sessionId, { terminal, fitAddon, inputDisposable, resizeDisposable }>
|
||||
const terminalInstances = new Map();
|
||||
|
||||
// Data listeners per session - Map<sessionId, { onData, onExit }>
|
||||
const sessionListeners = new Map();
|
||||
|
||||
// Parking host for terminal DOM when view unmounts
|
||||
let parkingHost = null;
|
||||
|
||||
// Export function to get current session ID (for backward compatibility)
|
||||
export const getSessionId = () => {
|
||||
// Return the first active session ID if any
|
||||
if (terminalInstances.size > 0) {
|
||||
return Array.from(terminalInstances.keys())[0];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const ensureParkingHost = () => {
|
||||
if (parkingHost && document.body.contains(parkingHost)) return parkingHost;
|
||||
parkingHost = document.createElement('div');
|
||||
parkingHost.style.display = 'none';
|
||||
parkingHost.setAttribute('data-terminal-parking-host', 'true');
|
||||
document.body.appendChild(parkingHost);
|
||||
return parkingHost;
|
||||
};
|
||||
|
||||
const createTerminalForSession = (sessionId) => {
|
||||
if (terminalInstances.has(sessionId)) {
|
||||
return terminalInstances.get(sessionId);
|
||||
}
|
||||
|
||||
const terminal = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
theme: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4',
|
||||
cursor: '#d4d4d4',
|
||||
selection: '#264f78',
|
||||
black: '#1e1e1e',
|
||||
red: '#f14c4c',
|
||||
green: '#23d18b',
|
||||
yellow: '#f5f543',
|
||||
blue: '#3b8eea',
|
||||
magenta: '#d670d6',
|
||||
cyan: '#29b8db',
|
||||
white: '#e5e5e5',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#f14c4c',
|
||||
brightGreen: '#23d18b',
|
||||
brightYellow: '#f5f543',
|
||||
brightBlue: '#3b8eea',
|
||||
brightMagenta: '#d670d6',
|
||||
brightCyan: '#29b8db',
|
||||
brightWhite: '#e5e5e5'
|
||||
},
|
||||
allowProposedApi: true
|
||||
});
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
|
||||
const inputDisposable = terminal.onData((data) => {
|
||||
if (data && sessionId && window.ipcRenderer) {
|
||||
window.ipcRenderer.send('terminal:input', sessionId, data);
|
||||
}
|
||||
});
|
||||
|
||||
const resizeDisposable = terminal.onResize(({ cols, rows }) => {
|
||||
if (sessionId && window.ipcRenderer) {
|
||||
window.ipcRenderer.send('terminal:resize', sessionId, { cols, rows });
|
||||
}
|
||||
});
|
||||
|
||||
const instance = {
|
||||
terminal,
|
||||
fitAddon,
|
||||
inputDisposable,
|
||||
resizeDisposable
|
||||
};
|
||||
|
||||
terminalInstances.set(sessionId, instance);
|
||||
|
||||
// Setup IPC listeners for this session
|
||||
if (window.ipcRenderer && !sessionListeners.has(sessionId)) {
|
||||
const onData = (data) => {
|
||||
if (!data) return;
|
||||
const instance = terminalInstances.get(sessionId);
|
||||
if (instance && instance.terminal) {
|
||||
try {
|
||||
instance.terminal.write(data);
|
||||
} catch (err) {
|
||||
console.warn('Failed to write terminal data:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onExit = ({ exitCode, signal } = {}) => {
|
||||
const msg = `\r\n[Process exited with code ${exitCode ?? ''} ${signal ? `(signal ${signal})` : ''}]\r\n`;
|
||||
const instance = terminalInstances.get(sessionId);
|
||||
if (instance && instance.terminal) {
|
||||
try {
|
||||
instance.terminal.write(msg);
|
||||
} catch (err) {
|
||||
console.warn('Failed to write terminal exit message:', err);
|
||||
}
|
||||
}
|
||||
// Cleanup on exit
|
||||
cleanupTerminalInstance(sessionId);
|
||||
};
|
||||
|
||||
window.ipcRenderer.on(`terminal:data:${sessionId}`, onData);
|
||||
window.ipcRenderer.on(`terminal:exit:${sessionId}`, onExit);
|
||||
|
||||
sessionListeners.set(sessionId, { onData, onExit });
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const cleanupTerminalInstance = (sessionId) => {
|
||||
const instance = terminalInstances.get(sessionId);
|
||||
if (instance) {
|
||||
try {
|
||||
if (instance.inputDisposable) instance.inputDisposable.dispose();
|
||||
if (instance.resizeDisposable) instance.resizeDisposable.dispose();
|
||||
if (instance.terminal) {
|
||||
instance.terminal.dispose();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Error disposing terminal instance:', err);
|
||||
}
|
||||
terminalInstances.delete(sessionId);
|
||||
}
|
||||
|
||||
// Remove IPC listeners
|
||||
const listeners = sessionListeners.get(sessionId);
|
||||
if (listeners && window.ipcRenderer) {
|
||||
try {
|
||||
window.ipcRenderer.removeAllListeners(`terminal:data:${sessionId}`);
|
||||
window.ipcRenderer.removeAllListeners(`terminal:exit:${sessionId}`);
|
||||
} catch (err) {
|
||||
console.warn('Error removing IPC listeners:', err);
|
||||
}
|
||||
sessionListeners.delete(sessionId);
|
||||
}
|
||||
};
|
||||
|
||||
const openTerminalIntoContainer = async (container, sessionId) => {
|
||||
if (!container || !sessionId) return;
|
||||
|
||||
const instance = createTerminalForSession(sessionId);
|
||||
const { terminal, fitAddon } = instance;
|
||||
|
||||
if (!terminal.element) {
|
||||
terminal.open(container);
|
||||
} else {
|
||||
// Move terminal element to new container
|
||||
if (terminal.element.parentElement !== container) {
|
||||
container.appendChild(terminal.element);
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
try {
|
||||
fitAddon.fit();
|
||||
const { cols, rows } = terminal;
|
||||
if (cols && rows && window.ipcRenderer) {
|
||||
window.ipcRenderer.send('terminal:resize', sessionId, { cols, rows });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error fitting terminal:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const TerminalTab = () => {
|
||||
const terminalRef = useRef(null);
|
||||
const [sessions, setSessions] = useState([]);
|
||||
const [activeSessionId, setActiveSessionId] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Load sessions list
|
||||
const loadSessions = useCallback(async (currentActiveSessionId = null) => {
|
||||
if (!window.ipcRenderer) return [];
|
||||
|
||||
try {
|
||||
const sessionList = await window.ipcRenderer.invoke('terminal:list-sessions');
|
||||
setSessions(sessionList);
|
||||
|
||||
// Use functional state updates to get the current activeSessionId
|
||||
setActiveSessionId((prevActiveSessionId) => {
|
||||
const activeId = currentActiveSessionId !== null ? currentActiveSessionId : prevActiveSessionId;
|
||||
|
||||
// Auto-select first session if none selected
|
||||
if (!activeId && sessionList.length > 0) {
|
||||
return sessionList[0].sessionId;
|
||||
}
|
||||
|
||||
// If active session no longer exists, select first available
|
||||
if (activeId && !sessionList.find((s) => s.sessionId === activeId)) {
|
||||
return sessionList.length > 0 ? sessionList[0].sessionId : null;
|
||||
}
|
||||
|
||||
// Keep current selection if it still exists
|
||||
return activeId;
|
||||
});
|
||||
|
||||
return sessionList;
|
||||
} catch (err) {
|
||||
console.error('Failed to load sessions:', err);
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Create new terminal session
|
||||
const createNewSession = useCallback(async (cwd = null) => {
|
||||
if (!window.ipcRenderer) return null;
|
||||
|
||||
try {
|
||||
const options = cwd ? { cwd } : {};
|
||||
const newSessionId = await window.ipcRenderer.invoke('terminal:create', options);
|
||||
if (newSessionId) {
|
||||
await loadSessions(newSessionId);
|
||||
setActiveSessionId(newSessionId);
|
||||
return newSessionId;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create terminal session:', err);
|
||||
}
|
||||
return null;
|
||||
}, [loadSessions]);
|
||||
|
||||
// Listen for requests to open terminal at specific CWD
|
||||
useEffect(() => {
|
||||
const normalizePath = (path) => {
|
||||
if (!path) return '';
|
||||
// Normalize path separators and remove trailing separators for comparison
|
||||
return path.replace(/\\/g, '/').replace(/\/$/, '') || '/';
|
||||
};
|
||||
|
||||
const handleOpenTerminalAtCwd = async (event) => {
|
||||
const { cwd } = event.detail;
|
||||
if (!cwd) return;
|
||||
|
||||
const normalizedCwd = normalizePath(cwd);
|
||||
|
||||
// Check if session already exists at this CWD
|
||||
const sessionList = await window.ipcRenderer.invoke('terminal:list-sessions');
|
||||
const existingSession = sessionList.find((s) => normalizePath(s.cwd) === normalizedCwd);
|
||||
|
||||
if (existingSession) {
|
||||
// Switch to existing session
|
||||
await loadSessions(existingSession.sessionId);
|
||||
setActiveSessionId(existingSession.sessionId);
|
||||
} else {
|
||||
// Create new session at this CWD
|
||||
await createNewSession(cwd);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('terminal:open-at-cwd', handleOpenTerminalAtCwd);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('terminal:open-at-cwd', handleOpenTerminalAtCwd);
|
||||
};
|
||||
}, [loadSessions, createNewSession]);
|
||||
|
||||
// Close terminal session
|
||||
const closeSession = async (sessionId) => {
|
||||
if (!window.ipcRenderer) return;
|
||||
|
||||
try {
|
||||
window.ipcRenderer.send('terminal:kill', sessionId);
|
||||
cleanupTerminalInstance(sessionId);
|
||||
|
||||
// Load updated sessions (this will also handle active session switching)
|
||||
const updatedSessions = await loadSessions();
|
||||
|
||||
// If we closed the active session and there are no sessions left, clear selection
|
||||
if (activeSessionId === sessionId && updatedSessions.length === 0) {
|
||||
setActiveSessionId(null);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to close terminal session:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Load sessions on mount and set up polling
|
||||
useEffect(() => {
|
||||
if (!window.ipcRenderer) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let mounted = true;
|
||||
|
||||
const initialLoad = async () => {
|
||||
const sessionList = await loadSessions();
|
||||
if (mounted) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
initialLoad();
|
||||
|
||||
// Poll for session updates every 2 seconds
|
||||
// Note: We don't pass currentActiveSessionId here to avoid stale closures
|
||||
// The functional update inside loadSessions will use the current state
|
||||
const pollInterval = setInterval(() => {
|
||||
if (mounted) {
|
||||
loadSessions();
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
clearInterval(pollInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle terminal display for active session
|
||||
useEffect(() => {
|
||||
if (!activeSessionId || !terminalRef.current) return;
|
||||
|
||||
let mounted = true;
|
||||
|
||||
const setupTerminal = async () => {
|
||||
await openTerminalIntoContainer(terminalRef.current, activeSessionId);
|
||||
|
||||
if (mounted) {
|
||||
const instance = terminalInstances.get(activeSessionId);
|
||||
if (instance && instance.fitAddon) {
|
||||
const onResize = () => {
|
||||
try {
|
||||
instance.fitAddon.fit();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
// Initial resize
|
||||
setTimeout(() => {
|
||||
try {
|
||||
instance.fitAddon.fit();
|
||||
const { cols, rows } = instance.terminal;
|
||||
if (cols && rows && window.ipcRenderer) {
|
||||
window.ipcRenderer.send('terminal:resize', activeSessionId, { cols, rows });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to perform initial resize:', err);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
|
||||
// Park terminal element when switching sessions
|
||||
if (instance.terminal && instance.terminal.element) {
|
||||
const host = ensureParkingHost();
|
||||
if (instance.terminal.element.parentElement !== host) {
|
||||
host.appendChild(instance.terminal.element);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = setupTerminal();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
Promise.resolve(cleanup).then((fn) => {
|
||||
if (typeof fn === 'function') fn();
|
||||
});
|
||||
};
|
||||
}, [activeSessionId]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="terminal-content">
|
||||
{/* Left Sidebar */}
|
||||
<div className="terminal-sessions-sidebar">
|
||||
<div className="terminal-sessions-header">
|
||||
<span>Sessions</span>
|
||||
<IconPlus
|
||||
size={16}
|
||||
style={{ cursor: 'pointer', color: '#888' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
createNewSession();
|
||||
}}
|
||||
title="New Terminal Session"
|
||||
/>
|
||||
</div>
|
||||
<div className="terminal-sessions-list">
|
||||
{isLoading ? (
|
||||
<div style={{ padding: '12px', color: '#888', fontSize: '13px' }}>
|
||||
Loading sessions...
|
||||
</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<div style={{ padding: '12px', color: '#888', fontSize: '13px' }}>
|
||||
No active sessions
|
||||
</div>
|
||||
) : (
|
||||
<SessionList
|
||||
sessions={sessions}
|
||||
activeSessionId={activeSessionId}
|
||||
onSelectSession={setActiveSessionId}
|
||||
onCloseSession={closeSession}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Terminal Display */}
|
||||
<div className="terminal-display-container">
|
||||
{!activeSessionId && window.ipcRenderer && (
|
||||
<div className="terminal-loading">
|
||||
<IconTerminal2 size={24} strokeWidth={1.5} />
|
||||
<span>No terminal session selected</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="terminal-container"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: activeSessionId ? 'block' : 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TerminalTab;
|
||||
@@ -2,12 +2,12 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import {
|
||||
IconX,
|
||||
IconTrash,
|
||||
import {
|
||||
IconX,
|
||||
IconTrash,
|
||||
IconFilter,
|
||||
IconAlertTriangle,
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
IconAlertCircle,
|
||||
IconBug,
|
||||
IconCode,
|
||||
IconChevronDown,
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
IconNetwork,
|
||||
IconDashboard
|
||||
} from '@tabler/icons';
|
||||
import {
|
||||
closeConsole,
|
||||
clearLogs,
|
||||
updateFilter,
|
||||
import {
|
||||
closeConsole,
|
||||
clearLogs,
|
||||
updateFilter,
|
||||
toggleAllFilters,
|
||||
setActiveTab,
|
||||
clearDebugErrors,
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from 'providers/ReduxStore/slices/logs';
|
||||
|
||||
import NetworkTab from './NetworkTab';
|
||||
import TerminalTab from './TerminalTab';
|
||||
import RequestDetailsPanel from './RequestDetailsPanel';
|
||||
// import DebugTab from './DebugTab';
|
||||
import ErrorDetailsPanel from './ErrorDetailsPanel';
|
||||
@@ -35,7 +36,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const LogIcon = ({ type }) => {
|
||||
const iconProps = { size: 16, strokeWidth: 1.5 };
|
||||
|
||||
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return <IconAlertCircle className="log-icon error" {...iconProps} />;
|
||||
@@ -52,20 +53,20 @@ const LogIcon = ({ type }) => {
|
||||
|
||||
const LogTimestamp = ({ timestamp }) => {
|
||||
const date = new Date(timestamp);
|
||||
const time = date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
const time = date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 3
|
||||
});
|
||||
|
||||
|
||||
return <span className="log-timestamp">{time}</span>;
|
||||
};
|
||||
|
||||
const LogMessage = ({ message, args }) => {
|
||||
const { displayedTheme } = useTheme();
|
||||
|
||||
|
||||
const formatMessage = (msg, originalArgs) => {
|
||||
if (originalArgs && originalArgs.length > 0) {
|
||||
return originalArgs.map((arg, index) => {
|
||||
@@ -84,7 +85,7 @@ const LogMessage = ({ message, args }) => {
|
||||
name={false}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: '12px',
|
||||
fontSize: '${(props) => props.theme.font.size.sm}',
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace'
|
||||
}}
|
||||
/>
|
||||
@@ -98,7 +99,7 @@ const LogMessage = ({ message, args }) => {
|
||||
};
|
||||
|
||||
const formattedMessage = formatMessage(message, args);
|
||||
|
||||
|
||||
return (
|
||||
<span className="log-message">
|
||||
{Array.isArray(formattedMessage) ? formattedMessage.map((item, index) => (
|
||||
@@ -112,7 +113,7 @@ const FilterDropdown = ({ filters, logCounts, onFilterToggle, onToggleAll }) =>
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const allFiltersEnabled = Object.values(filters).every(f => f);
|
||||
const allFiltersEnabled = Object.values(filters).every((f) => f);
|
||||
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -128,7 +129,7 @@ const FilterDropdown = ({ filters, logCounts, onFilterToggle, onToggleAll }) =>
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown" ref={dropdownRef}>
|
||||
<button
|
||||
<button
|
||||
className="filter-dropdown-trigger"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
title="Filter logs by type"
|
||||
@@ -139,19 +140,19 @@ const FilterDropdown = ({ filters, logCounts, onFilterToggle, onToggleAll }) =>
|
||||
</span>
|
||||
<IconChevronDown size={14} strokeWidth={1.5} />
|
||||
</button>
|
||||
|
||||
|
||||
{isOpen && (
|
||||
<div className={`filter-dropdown-menu right`}>
|
||||
<div className="filter-dropdown-menu right">
|
||||
<div className="filter-dropdown-header">
|
||||
<span>Filter by Type</span>
|
||||
<button
|
||||
<button
|
||||
className="filter-toggle-all"
|
||||
onClick={() => onToggleAll(!allFiltersEnabled)}
|
||||
>
|
||||
{allFiltersEnabled ? 'Hide All' : 'Show All'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="filter-dropdown-options">
|
||||
{Object.entries(filters).map(([filterType, enabled]) => (
|
||||
<label key={filterType} className="filter-option">
|
||||
@@ -178,7 +179,7 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const allFiltersEnabled = Object.values(filters).every(f => f);
|
||||
const allFiltersEnabled = Object.values(filters).every((f) => f);
|
||||
const activeFilters = Object.entries(filters).filter(([_, enabled]) => enabled);
|
||||
|
||||
const getMethodColor = (method) => {
|
||||
@@ -207,7 +208,7 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown" ref={dropdownRef}>
|
||||
<button
|
||||
<button
|
||||
className="filter-dropdown-trigger"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
title="Filter requests by method"
|
||||
@@ -218,19 +219,19 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
</span>
|
||||
<IconChevronDown size={14} strokeWidth={1.5} />
|
||||
</button>
|
||||
|
||||
|
||||
{isOpen && (
|
||||
<div className={`filter-dropdown-menu right`}>
|
||||
<div className="filter-dropdown-menu right">
|
||||
<div className="filter-dropdown-header">
|
||||
<span>Filter by Method</span>
|
||||
<button
|
||||
<button
|
||||
className="filter-toggle-all"
|
||||
onClick={() => onToggleAll(!allFiltersEnabled)}
|
||||
>
|
||||
{allFiltersEnabled ? 'Hide All' : 'Show All'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="filter-dropdown-options">
|
||||
{Object.entries(filters).map(([method, enabled]) => (
|
||||
<label key={method} className="filter-option">
|
||||
@@ -258,7 +259,7 @@ const NetworkFilterDropdown = ({ filters, requestCounts, onFilterToggle, onToggl
|
||||
const ConsoleTab = ({ logs, filters, logCounts, onFilterToggle, onToggleAll, onClearLogs }) => {
|
||||
const logsEndRef = useRef(null);
|
||||
const prevLogsCountRef = useRef(0);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// Only scroll when new logs are added, not when switching tabs
|
||||
if (logsEndRef.current && logs.length > prevLogsCountRef.current) {
|
||||
@@ -267,7 +268,7 @@ const ConsoleTab = ({ logs, filters, logCounts, onFilterToggle, onToggleAll, onC
|
||||
prevLogsCountRef.current = logs.length;
|
||||
}, [logs]);
|
||||
|
||||
const filteredLogs = logs.filter(log => filters[log.type]);
|
||||
const filteredLogs = logs.filter((log) => filters[log.type]);
|
||||
|
||||
return (
|
||||
<div className="tab-content">
|
||||
@@ -299,8 +300,8 @@ const ConsoleTab = ({ logs, filters, logCounts, onFilterToggle, onToggleAll, onC
|
||||
|
||||
const Console = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { logs, filters, activeTab, selectedRequest, selectedError, networkFilters, debugErrors } = useSelector(state => state.logs);
|
||||
const collections = useSelector(state => state.collections.collections);
|
||||
const { logs, filters, activeTab, selectedRequest, selectedError, networkFilters, debugErrors } = useSelector((state) => state.logs);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
const consoleRef = useRef(null);
|
||||
|
||||
const logCounts = logs.reduce((counts, log) => {
|
||||
@@ -310,12 +311,12 @@ const Console = () => {
|
||||
|
||||
const allRequests = React.useMemo(() => {
|
||||
const requests = [];
|
||||
|
||||
collections.forEach(collection => {
|
||||
|
||||
collections.forEach((collection) => {
|
||||
if (collection.timeline) {
|
||||
collection.timeline
|
||||
.filter(entry => entry.type === 'request')
|
||||
.forEach(entry => {
|
||||
.filter((entry) => entry.type === 'request')
|
||||
.forEach((entry) => {
|
||||
requests.push({
|
||||
...entry,
|
||||
collectionName: collection.name,
|
||||
@@ -324,12 +325,12 @@ const Console = () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return requests.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}, [collections]);
|
||||
|
||||
const filteredLogs = logs.filter(log => filters[log.type]);
|
||||
const filteredRequests = allRequests.filter(request => {
|
||||
const filteredLogs = logs.filter((log) => filters[log.type]);
|
||||
const filteredRequests = allRequests.filter((request) => {
|
||||
const method = request.data?.request?.method?.toUpperCase() || 'GET';
|
||||
return networkFilters[method];
|
||||
});
|
||||
@@ -389,6 +390,8 @@ const Console = () => {
|
||||
return <NetworkTab />;
|
||||
case 'performance':
|
||||
return <Performance />;
|
||||
case 'terminal':
|
||||
return <TerminalTab />;
|
||||
// case 'debug':
|
||||
// return <DebugTab />;
|
||||
default:
|
||||
@@ -419,7 +422,7 @@ const Console = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className="action-controls">
|
||||
<button
|
||||
<button
|
||||
className="control-button"
|
||||
onClick={handleClearLogs}
|
||||
title="Clear all logs"
|
||||
@@ -442,12 +445,14 @@ const Console = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case 'terminal':
|
||||
return null; // No controls needed for terminal
|
||||
// case 'debug':
|
||||
// return (
|
||||
// <div className="tab-controls">
|
||||
// <div className="action-controls">
|
||||
// {debugErrors.length > 0 && (
|
||||
// <button
|
||||
// <button
|
||||
// className="control-button"
|
||||
// onClick={handleClearDebugErrors}
|
||||
// title="Clear all errors"
|
||||
@@ -463,32 +468,30 @@ const Console = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper ref={consoleRef}>
|
||||
<div
|
||||
<div
|
||||
className="console-resize-handle"
|
||||
/>
|
||||
|
||||
|
||||
<div className="console-header">
|
||||
<div className="console-tabs">
|
||||
<button
|
||||
<button
|
||||
className={`console-tab ${activeTab === 'console' ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange('console')}
|
||||
>
|
||||
<IconTerminal2 size={16} strokeWidth={1.5} />
|
||||
<span>Console</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
<button
|
||||
className={`console-tab ${activeTab === 'network' ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange('network')}
|
||||
>
|
||||
<IconNetwork size={16} strokeWidth={1.5} />
|
||||
<span>Network</span>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
className={`console-tab ${activeTab === 'performance' ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange('performance')}
|
||||
@@ -497,7 +500,15 @@ const Console = () => {
|
||||
<span>Performance</span>
|
||||
</button>
|
||||
|
||||
{/* <button
|
||||
<button
|
||||
className={`console-tab ${activeTab === 'terminal' ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange('terminal')}
|
||||
>
|
||||
<IconTerminal2 size={16} strokeWidth={1.5} />
|
||||
<span>Terminal</span>
|
||||
</button>
|
||||
|
||||
{/* <button
|
||||
className={`console-tab ${activeTab === 'debug' ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange('debug')}
|
||||
>
|
||||
@@ -508,7 +519,7 @@ const Console = () => {
|
||||
|
||||
<div className="console-controls">
|
||||
{renderTabControls()}
|
||||
<button
|
||||
<button
|
||||
className="control-button close-button"
|
||||
onClick={handlecloseConsole}
|
||||
title="Close console"
|
||||
@@ -541,4 +552,4 @@ const Console = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Console;
|
||||
export default Console;
|
||||
|
||||
@@ -35,13 +35,13 @@ const StyledWrapper = styled.div`
|
||||
h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.console.textMuted};
|
||||
}
|
||||
}
|
||||
@@ -51,8 +51,8 @@ const StyledWrapper = styled.div`
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
}
|
||||
@@ -80,19 +80,19 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 12px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.resource-subtitle {
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
margin-top: 8px;
|
||||
|
||||
&.up {
|
||||
@@ -115,6 +115,237 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
}
|
||||
|
||||
.performance-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
padding: 12px 16px;
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
}
|
||||
|
||||
.performance-selector-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.performance-selector-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.performance-selector {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.performance-select {
|
||||
appearance: none;
|
||||
background: ${(props) => props.theme.console.bg};
|
||||
border: 1px solid ${(props) => props.theme.console.border};
|
||||
border-radius: 4px;
|
||||
padding: 6px 32px 6px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 250px;
|
||||
max-width: 400px;
|
||||
|
||||
&:hover {
|
||||
border-color: ${(props) => props.theme.colors.primary};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: ${(props) => props.theme.colors.primary};
|
||||
box-shadow: 0 0 0 2px ${(props) => props.theme.colors.primary}33;
|
||||
}
|
||||
|
||||
option {
|
||||
background: ${(props) => props.theme.console.bg};
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.performance-select-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
pointer-events: none;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
|
||||
.processes-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.no-processes {
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.processes-table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.processes-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
border: 1px solid ${(props) => props.theme.console.border};
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
thead {
|
||||
background: ${(props) => props.theme.console.bg};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
|
||||
th {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.console.bg};
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
color: ${(props) => props.theme.console.textColor};
|
||||
|
||||
&:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pid-cell {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
|
||||
.type-cell {
|
||||
.process-type-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: lowercase;
|
||||
background: ${(props) => props.theme.console.border};
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
|
||||
&.Browser {
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
&.Renderer {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
&.Utility {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
&.Zygote {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
&.Sandbox {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-cell {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cpu-cell {
|
||||
font-weight: 500;
|
||||
|
||||
.high-cpu {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.medium-cpu {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.low-cpu {
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
}
|
||||
|
||||
.memory-cell {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.created-cell {
|
||||
font-size: 12px;
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import {
|
||||
@@ -6,11 +6,23 @@ import {
|
||||
IconDatabase,
|
||||
IconClock,
|
||||
IconServer,
|
||||
IconChevronDown,
|
||||
IconChartLine
|
||||
} from '@tabler/icons';
|
||||
|
||||
const getProcessOptions = (processes) => {
|
||||
return [
|
||||
{ value: 'cumulative', label: 'Cumulative (All Processes)' },
|
||||
...(processes ?? []).map((process) => ({
|
||||
value: String(process.pid),
|
||||
label: `PID ${process.pid}${process.title ? ` - ${process.title}` : ''}${process.type ? ` (${process.type})` : ''}`
|
||||
}))
|
||||
];
|
||||
};
|
||||
|
||||
const Performance = () => {
|
||||
const { systemResources } = useSelector((state) => state.performance);
|
||||
const [selectedPid, setSelectedPid] = useState('cumulative');
|
||||
|
||||
useEffect(() => {
|
||||
const { ipcRenderer } = window;
|
||||
@@ -82,47 +94,140 @@ const Performance = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
// Get process options for dropdown
|
||||
const processOptions = useMemo(() => getProcessOptions(systemResources.processes), [systemResources.processes]);
|
||||
|
||||
// Get selected process data
|
||||
const selectedProcess = useMemo(() => {
|
||||
if (selectedPid === 'cumulative') {
|
||||
return null; // Show cumulative view
|
||||
}
|
||||
const processes = systemResources.processes || [];
|
||||
return processes.find((p) => String(p.pid) === selectedPid) || null;
|
||||
}, [selectedPid, systemResources.processes]);
|
||||
|
||||
// Reset to cumulative if selected PID no longer exists
|
||||
useEffect(() => {
|
||||
if (selectedPid !== 'cumulative' && !selectedProcess) {
|
||||
setSelectedPid('cumulative');
|
||||
}
|
||||
}, [selectedPid, selectedProcess]);
|
||||
|
||||
const renderCumulativeView = () => (
|
||||
<div className="system-resources">
|
||||
<h2>System Resources</h2>
|
||||
<div className="resource-cards">
|
||||
<SystemResourceCard
|
||||
icon={IconCpu}
|
||||
title="CPU Usage"
|
||||
value={`${systemResources.cpu.toFixed(1)}%`}
|
||||
subtitle="Total CPU usage"
|
||||
color={systemResources.cpu > 80 ? 'danger' : systemResources.cpu > 60 ? 'warning' : 'success'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconDatabase}
|
||||
title="Memory Usage"
|
||||
value={formatBytes(systemResources.memory)}
|
||||
subtitle="Total memory usage"
|
||||
color={systemResources.memory > (500 * 1024 * 1024) ? 'danger' : 'default'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconClock}
|
||||
title="Uptime"
|
||||
value={formatUptime(systemResources.uptime)}
|
||||
subtitle="Process runtime"
|
||||
color="info"
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconServer}
|
||||
title="Process ID"
|
||||
value={systemResources.pid || 'N/A'}
|
||||
subtitle="Main process PID"
|
||||
color="default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderProcessView = (process) => {
|
||||
if (!process) return null;
|
||||
|
||||
// Calculate uptime for individual process
|
||||
const processUptime = process.creationTime
|
||||
? (new Date() - new Date(process.creationTime)) / 1000
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div className="system-resources">
|
||||
<h2>System Resources</h2>
|
||||
<div className="resource-cards">
|
||||
<SystemResourceCard
|
||||
icon={IconCpu}
|
||||
title="CPU Usage"
|
||||
value={`${process.cpu.toFixed(1)}%`}
|
||||
subtitle="Current CPU usage"
|
||||
color={process.cpu > 80 ? 'danger' : process.cpu > 60 ? 'warning' : 'success'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconDatabase}
|
||||
title="Memory Usage"
|
||||
value={formatBytes(process.memory)}
|
||||
subtitle="Current memory usage"
|
||||
color={process.memory > (500 * 1024 * 1024) ? 'danger' : 'default'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconClock}
|
||||
title="Uptime"
|
||||
value={formatUptime(processUptime)}
|
||||
subtitle="Process runtime"
|
||||
color="info"
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconServer}
|
||||
title="Process ID"
|
||||
value={process.pid}
|
||||
subtitle="Process PID"
|
||||
color="default"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="tab-content">
|
||||
<div className="tab-content-area">
|
||||
<div className="system-resources">
|
||||
<h2>System Resources</h2>
|
||||
<div className="resource-cards">
|
||||
<SystemResourceCard
|
||||
icon={IconCpu}
|
||||
title="CPU Usage"
|
||||
value={`${systemResources.cpu.toFixed(1)}%`}
|
||||
subtitle="Current process"
|
||||
color={systemResources.cpu > 80 ? 'danger' : systemResources.cpu > 60 ? 'warning' : 'success'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconDatabase}
|
||||
title="Memory Usage"
|
||||
value={formatBytes(systemResources.memory)}
|
||||
subtitle="Current process"
|
||||
color={systemResources.memory > 500 * 1024 * 1024 ? 'danger' : 'default'}
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconClock}
|
||||
title="Uptime"
|
||||
value={formatUptime(systemResources.uptime)}
|
||||
subtitle="Process runtime"
|
||||
color="info"
|
||||
/>
|
||||
|
||||
<SystemResourceCard
|
||||
icon={IconServer}
|
||||
title="Process ID"
|
||||
value={systemResources.pid || 'N/A'}
|
||||
subtitle="Current PID"
|
||||
color="default"
|
||||
/>
|
||||
<div className="performance-header">
|
||||
<div className="performance-selector-wrapper">
|
||||
<label htmlFor="process-selector" className="performance-selector-label">
|
||||
View:
|
||||
</label>
|
||||
<div className="performance-selector">
|
||||
<select
|
||||
id="process-selector"
|
||||
value={selectedPid}
|
||||
onChange={(e) => setSelectedPid(e.target.value)}
|
||||
className="performance-select"
|
||||
>
|
||||
{processOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<IconChevronDown size={16} className="performance-select-icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tab-content-area">
|
||||
{selectedPid === 'cumulative' ? renderCumulativeView() : renderProcessView(selectedProcess)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -18,11 +18,11 @@ const Devtools = ({ mainSectionRef }) => {
|
||||
|
||||
const handleDevtoolsResize = useCallback((e) => {
|
||||
if (!isResizingDevtools || !mainSectionRef.current) return;
|
||||
|
||||
|
||||
const windowHeight = window.innerHeight;
|
||||
const statusBarHeight = 22;
|
||||
const mouseY = e.clientY;
|
||||
|
||||
|
||||
// Calculate new devtools height - expanding upward from bottom
|
||||
const newHeight = windowHeight - mouseY - statusBarHeight;
|
||||
const clampedHeight = Math.min(MAX_DEVTOOLS_HEIGHT, Math.max(MIN_DEVTOOLS_HEIGHT, newHeight));
|
||||
@@ -43,7 +43,7 @@ const Devtools = ({ mainSectionRef }) => {
|
||||
document.addEventListener('mousemove', handleDevtoolsResize);
|
||||
document.addEventListener('mouseup', handleDevtoolsResizeEnd);
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleDevtoolsResize);
|
||||
document.removeEventListener('mouseup', handleDevtoolsResizeEnd);
|
||||
@@ -65,7 +65,7 @@ const Devtools = ({ mainSectionRef }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
<div
|
||||
onMouseDown={handleDevtoolsResizeStart}
|
||||
style={{
|
||||
height: '4px',
|
||||
@@ -85,4 +85,4 @@ const Devtools = ({ mainSectionRef }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Devtools;
|
||||
export default Devtools;
|
||||
|
||||
@@ -8,44 +8,66 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.tippy-box {
|
||||
min-width: 135px;
|
||||
font-size: 0.8125rem;
|
||||
min-width: 160px;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
background-color: ${(props) => props.theme.dropdown.bg};
|
||||
box-shadow: ${(props) => props.theme.dropdown.shadow};
|
||||
border-radius: 3px;
|
||||
box-shadow: ${(props) => props.theme.shadow.sm};
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
max-width: unset !important;
|
||||
padding: 0.25rem;
|
||||
|
||||
.tippy-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
.label-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.6rem;
|
||||
background-color: ${(props) => props.theme.dropdown.labelBg};
|
||||
padding: 0.375rem 0.625rem 0.25rem 0.625rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
opacity: 0.6;
|
||||
margin-top: 0.25rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.6rem;
|
||||
gap: 0.5rem;
|
||||
padding: 0.275rem 0.625rem;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
margin: 0.0625rem 0;
|
||||
font-size: 0.8125rem;
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||
.icon {
|
||||
.dropdown-icon {
|
||||
color: ${(props) => props.theme.colors.text.yellow} !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
.dropdown-icon {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${(props) => props.theme.dropdown.iconColor};
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
@@ -54,13 +76,39 @@ const Wrapper = styled.div`
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: gray;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.delete-item {
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
.dropdown-icon {
|
||||
color: ${(props) => props.theme.colors.text.danger};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => {
|
||||
const hex = theme.colors.text.danger.replace('#', '');
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, 0.04)`; // 4% opacity
|
||||
}} !important;
|
||||
|
||||
color: ${(props) => props.theme.colors.text.danger} !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.border-top {
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-separator {
|
||||
height: 1px;
|
||||
background-color: ${(props) => props.theme.dropdown.separator};
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -146,7 +146,7 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
|
||||
{environments && environments.length > 0 ? (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex justify-between items-center mb-2 pb-1">
|
||||
<h3 className="font-semibold text-sm text-theme">
|
||||
<h3 className="font-medium text-theme">
|
||||
{environmentType === 'global' ? 'Global Environments' : 'Collection Environments'}
|
||||
</h3>
|
||||
<button
|
||||
@@ -175,7 +175,7 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
|
||||
) : (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex justify-between items-center mb-2 pb-1">
|
||||
<h3 className="font-semibold text-sm text-theme">
|
||||
<h3 className="font-medium text-theme">
|
||||
{environmentType === 'global' ? 'Global Environments' : 'Collection Environments'}
|
||||
</h3>
|
||||
</div>
|
||||
@@ -191,7 +191,7 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
|
||||
{/* Export Format Section */}
|
||||
{selectedCount > 0 && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium mb-2 text-theme">
|
||||
<label className="block font-medium mb-2 text-theme">
|
||||
Export Format
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
@@ -207,7 +207,7 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
|
||||
className={`mt-0.5 mr-3 w-4 h-4 ${option.disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
/>
|
||||
<div>
|
||||
<div className={`text-sm font-medium ${option.disabled ? 'text-muted' : 'text-theme'}`}>{option.label}</div>
|
||||
<div className={`font-medium ${option.disabled ? 'text-muted' : 'text-theme'}`}>{option.label}</div>
|
||||
<div className="text-xs text-muted">{option.description}</div>
|
||||
</div>
|
||||
</label>
|
||||
@@ -218,7 +218,7 @@ const ExportEnvironmentModal = ({ onClose, environments = [], environmentType })
|
||||
|
||||
{/* Location Input Section */}
|
||||
<div className="mb-4">
|
||||
<label htmlFor="export-location" className="block text-sm font-medium mb-2 text-theme">
|
||||
<label htmlFor="export-location" className="block font-medium mb-2 text-theme">
|
||||
Location
|
||||
</label>
|
||||
<div className="flex flex-col relative items-center">
|
||||
|
||||
@@ -150,7 +150,7 @@ const ImportEnvironmentModal = ({ type = 'collection', collection, onClose, onEn
|
||||
data-testid={importTestId}
|
||||
>
|
||||
<IconFileImport size={64} />
|
||||
<span className="mt-2 block text-sm font-semibold">
|
||||
<span className="mt-2 block font-medium">
|
||||
{isDragOver ? 'Drop your environment files here' : 'Import your environments'}
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-muted">
|
||||
|
||||
@@ -22,7 +22,7 @@ const Wrapper = styled.div`
|
||||
|
||||
.env-text {
|
||||
color: ${(props) => props.theme.dropdown.selectedColor};
|
||||
font-size: 0.875rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ const Wrapper = styled.div`
|
||||
|
||||
.env-text-inactive {
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
font-size: 0.875rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ const Wrapper = styled.div`
|
||||
max-width: 650px !important;
|
||||
min-height: 15.5rem;
|
||||
max-height: 75vh;
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ const Wrapper = styled.div`
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.6rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
@@ -111,7 +111,7 @@ const Wrapper = styled.div`
|
||||
|
||||
.tab-button {
|
||||
color: var(--color-tab-inactive);
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
|
||||
.tab-content-wrapper {
|
||||
position: relative;
|
||||
@@ -163,7 +163,7 @@ const Wrapper = styled.div`
|
||||
h3 {
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ const Wrapper = styled.div`
|
||||
p {
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
opacity: 0.75;
|
||||
font-size: 0.6875rem;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
max-width: 11.875rem;
|
||||
@@ -192,7 +192,7 @@ const Wrapper = styled.div`
|
||||
border-radius: 0.375rem;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -216,7 +216,7 @@ const Wrapper = styled.div`
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
color: ${(props) => props.theme.dropdown.primaryText};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
opacity: 0.75;
|
||||
|
||||
@@ -49,15 +49,15 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
};
|
||||
|
||||
// Get description based on active tab
|
||||
const description =
|
||||
activeTab === 'collection'
|
||||
const description
|
||||
= activeTab === 'collection'
|
||||
? 'Create your first environment to begin working with your collection.'
|
||||
: 'Create your first global environment to begin working across collections.';
|
||||
|
||||
// Environment selection handler
|
||||
const handleEnvironmentSelect = (environment) => {
|
||||
const action =
|
||||
activeTab === 'collection'
|
||||
const action
|
||||
= activeTab === 'collection'
|
||||
? selectEnvironment(environment ? environment.uid : null, collection.uid)
|
||||
: selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null });
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ const CopyEnvironment = ({ collection, environment, onClose }) => {
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title={'Copy Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<Modal size="sm" title="Copy Environment" confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
<label htmlFor="name" className="block font-medium">
|
||||
New Environment Name
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -12,9 +12,9 @@ const CreateEnvironment = ({ collection, onClose, onEnvironmentCreated }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
|
||||
const validateEnvironmentName = (name) => {
|
||||
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
|
||||
};
|
||||
const validateEnvironmentName = (name) => {
|
||||
return !collection?.environments?.some((env) => env?.name?.toLowerCase().trim() === name?.toLowerCase().trim());
|
||||
};
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
@@ -25,7 +25,7 @@ const CreateEnvironment = ({ collection, onClose, onEnvironmentCreated }) => {
|
||||
name: Yup.string()
|
||||
.min(1, 'Must be at least 1 character')
|
||||
.max(255, 'Must be 255 characters or less')
|
||||
.test('is-valid-filename', function(value) {
|
||||
.test('is-valid-filename', function (value) {
|
||||
const isValid = validateName(value);
|
||||
return isValid ? true : this.createError({ message: validateNameError(value) });
|
||||
})
|
||||
@@ -60,14 +60,14 @@ const CreateEnvironment = ({ collection, onClose, onEnvironmentCreated }) => {
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Create Environment'}
|
||||
title="Create Environment"
|
||||
confirmText="Create"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
<label htmlFor="name" className="block font-medium">
|
||||
Environment Name
|
||||
</label>
|
||||
<div className="flex items-center mt-2">
|
||||
|
||||
@@ -22,12 +22,12 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Delete Environment'}
|
||||
title="Delete Environment"
|
||||
confirmText="Delete"
|
||||
handleConfirm={onConfirm}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
|
||||
Are you sure you want to delete <span className="font-medium">{environment.name}</span> ?
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
</Portal>
|
||||
|
||||
@@ -22,7 +22,7 @@ const ConfirmSwitchEnv = ({ onCancel }) => {
|
||||
>
|
||||
<div className="flex items-center font-normal">
|
||||
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
|
||||
<h1 className="ml-2 text-lg font-medium">Hold on..</h1>
|
||||
</div>
|
||||
<div className="font-normal mt-4">You have unsaved changes in this environment.</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -27,7 +27,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
thead td {
|
||||
@@ -36,11 +36,11 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.tooltip-mod {
|
||||
font-size: 11px !important;
|
||||
font-size: ${(props) => props.theme.font.size.xs} !important;
|
||||
width: 150px !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
|
||||
let _collection = cloneDeep(collection);
|
||||
|
||||
|
||||
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
|
||||
_collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||
|
||||
@@ -39,7 +39,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
return result;
|
||||
}
|
||||
const varNames = new Set(nonSecretVars.map((v) => v.name));
|
||||
|
||||
|
||||
const checkSensitiveField = (obj, fieldPath) => {
|
||||
const value = get(obj, fieldPath);
|
||||
if (typeof value === 'string') {
|
||||
@@ -62,7 +62,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
sensitiveFields.forEach((fieldPath) => {
|
||||
checkSensitiveField(collectionObj, fieldPath);
|
||||
});
|
||||
|
||||
|
||||
const items = flattenItems(collection.items || []);
|
||||
items.forEach((item) => {
|
||||
const objToProcess = getObjectToProcess(item);
|
||||
@@ -123,7 +123,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
|
||||
<IconAlertCircle id={id} className="text-red-600 cursor-pointer" size={20} />
|
||||
<Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
|
||||
</span>
|
||||
);
|
||||
@@ -160,7 +160,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
|
||||
useEffect(() => {
|
||||
if (formik.dirty) {
|
||||
// Smooth scrolling to the changed parameter is temporarily disabled
|
||||
// Smooth scrolling to the changed parameter is temporarily disabled
|
||||
// due to UX issues when editing the first row in a long list of environment variables.
|
||||
// addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
@@ -221,6 +221,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
value={variable.value}
|
||||
isSecret={variable.secret}
|
||||
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||
enableBrunoVarInfo={false}
|
||||
/>
|
||||
</div>
|
||||
{!variable.secret && hasSensitiveUsage(variable.name) && (
|
||||
|
||||
@@ -29,7 +29,7 @@ const EnvironmentDetails = ({ environment, collection, setIsModified, onClose })
|
||||
<div className="flex">
|
||||
<div className="flex flex-grow items-center">
|
||||
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||
<span className="ml-1 font-semibold break-all">{environment.name}</span>
|
||||
<span className="ml-1 font-medium break-all">{environment.name}</span>
|
||||
</div>
|
||||
<div className="flex gap-x-2 pl-2">
|
||||
<ToolHint text="Edit Environment" toolhintId={`edit-${environment.uid}`}>
|
||||
|
||||
@@ -25,7 +25,7 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEnvironment) {
|
||||
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
|
||||
const _selectedEnvironment = environments?.find((env) => env?.uid === selectedEnvironment?.uid);
|
||||
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
|
||||
if (hasSelectedEnvironmentChanged) {
|
||||
setSelectedEnvironment(_selectedEnvironment);
|
||||
@@ -107,16 +107,16 @@ const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collecti
|
||||
</div>
|
||||
)}
|
||||
<div className="environments-sidebar flex flex-col">
|
||||
{environments &&
|
||||
environments.length &&
|
||||
environments.map((env) => (
|
||||
{environments
|
||||
&& environments.length
|
||||
&& environments.map((env) => (
|
||||
<ToolHint key={env.uid} text={env.name} toolhintId={env.uid} place="right">
|
||||
<div
|
||||
id={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
|
||||
>
|
||||
<span className="break-all">{env.name}</span>
|
||||
<span className="break-all">{env.name}</span>
|
||||
</div>
|
||||
</ToolHint>
|
||||
))}
|
||||
|
||||
@@ -20,7 +20,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
name: Yup.string()
|
||||
.min(1, 'must be at least 1 character')
|
||||
.max(255, 'Must be 255 characters or less')
|
||||
.test('is-valid-filename', function(value) {
|
||||
.test('is-valid-filename', function (value) {
|
||||
const isValid = validateName(value);
|
||||
return isValid ? true : this.createError({ message: validateNameError(value) });
|
||||
})
|
||||
@@ -53,14 +53,14 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Rename Environment'}
|
||||
title="Rename Environment"
|
||||
confirmText="Rename"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
|
||||
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
<label htmlFor="name" className="block font-medium">
|
||||
Environment Name
|
||||
</label>
|
||||
<input
|
||||
|
||||
@@ -12,7 +12,7 @@ export const SharedButton = ({ children, className, onClick }) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-medium text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
${className}`}
|
||||
>
|
||||
{children}
|
||||
@@ -24,7 +24,7 @@ const DefaultTab = ({ setTab }) => {
|
||||
return (
|
||||
<div className="text-center items-center flex flex-col">
|
||||
<IconFileAlert size={64} strokeWidth={1} />
|
||||
<span className="font-semibold mt-2">No environments found</span>
|
||||
<span className="font-medium mt-2">No environments found</span>
|
||||
<span className="font-extralight mt-2 text-zinc-500 dark:text-zinc-400">
|
||||
Get started by using the following buttons :
|
||||
</span>
|
||||
|
||||
@@ -33,7 +33,7 @@ class ErrorBoundary extends Component {
|
||||
}
|
||||
|
||||
const serializeArgs = (args) => {
|
||||
return args.map(arg => {
|
||||
return args.map((arg) => {
|
||||
try {
|
||||
if (arg === null) return 'null';
|
||||
if (arg === undefined) return 'undefined';
|
||||
@@ -65,12 +65,12 @@ const serializeArgs = (args) => {
|
||||
// Helper function to extract file and line info from stack trace
|
||||
const extractFileInfo = (stack) => {
|
||||
if (!stack) return { filename: null, lineno: null, colno: null };
|
||||
|
||||
|
||||
try {
|
||||
const lines = stack.split('\n');
|
||||
for (let line of lines) {
|
||||
if (line.includes('ErrorCapture') || line.trim() === 'Error') continue;
|
||||
|
||||
|
||||
const match = line.match(/(?:at\s+.*?\s+)?\(?([^)]+):(\d+):(\d+)\)?/);
|
||||
if (match) {
|
||||
return {
|
||||
@@ -83,7 +83,7 @@ const extractFileInfo = (stack) => {
|
||||
} catch (e) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
|
||||
|
||||
return { filename: null, lineno: null, colno: null };
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ const useGlobalErrorCapture = () => {
|
||||
|
||||
console.error = (...args) => {
|
||||
const currentStack = new Error().stack;
|
||||
|
||||
|
||||
originalConsoleError.apply(console, args);
|
||||
|
||||
if (currentStack && currentStack.includes('useIpcEvents.js')) {
|
||||
@@ -130,7 +130,7 @@ const useGlobalErrorCapture = () => {
|
||||
|
||||
const ErrorCapture = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
useGlobalErrorCapture();
|
||||
|
||||
const handleReactError = (errorData) => {
|
||||
@@ -144,4 +144,4 @@ const ErrorCapture = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorCapture;
|
||||
export default ErrorCapture;
|
||||
|
||||
@@ -18,7 +18,7 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
const title = filenames.map((v) => `- ${v}`).join('\n');
|
||||
|
||||
const browse = () => {
|
||||
dispatch(browseFiles([], [!isSingleFilePicker ? "multiSelections": ""]))
|
||||
dispatch(browseFiles([], [!isSingleFilePicker ? 'multiSelections' : '']))
|
||||
.then((filePaths) => {
|
||||
// If file is in the collection's directory, then we use relative path
|
||||
// Otherwise, we use the absolute path
|
||||
@@ -73,4 +73,4 @@ const FilePickerEditor = ({ value, onChange, collection, isSingleFilePicker = fa
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePickerEditor;
|
||||
export default FilePickerEditor;
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
@@ -19,4 +19,4 @@ const Wrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
export default Wrapper;
|
||||
|
||||
@@ -63,7 +63,7 @@ const Auth = ({ collection, folder }) => {
|
||||
|
||||
// Get path from collection to current folder
|
||||
const folderTreePath = getTreePathFromCollectionToItem(collection, folder);
|
||||
|
||||
|
||||
// Check parent folders to find closest auth configuration
|
||||
// Skip the last item which is the current folder
|
||||
for (let i = 0; i < folderTreePath.length - 1; i++) {
|
||||
@@ -172,8 +172,8 @@ const Auth = ({ collection, folder }) => {
|
||||
return (
|
||||
<>
|
||||
<GrantTypeSelector
|
||||
request={request}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
updateAuth={updateFolderAuth}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
@@ -200,7 +200,6 @@ const Auth = ({ collection, folder }) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
@@ -220,4 +219,4 @@ const Auth = ({ collection, folder }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Auth;
|
||||
export default Auth;
|
||||
|
||||
@@ -5,7 +5,7 @@ const StyledWrapper = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.colors.border};
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
.auth-mode-label {
|
||||
@@ -13,4 +13,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -34,7 +34,7 @@ const AuthMode = ({ collection, folder }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -14,7 +14,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
@@ -31,7 +31,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.btn-add-header {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
|
||||
@@ -41,7 +41,8 @@ const Headers = ({ collection, folder }) => {
|
||||
const header = cloneDeep(_header);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
header.name = e.target.value;
|
||||
// Strip newlines from header keys
|
||||
header.name = e.target.value.replace(/[\r\n]/g, '');
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
@@ -120,8 +121,7 @@ const Headers = ({ collection, folder }) => {
|
||||
},
|
||||
header,
|
||||
'name'
|
||||
)
|
||||
}
|
||||
)}
|
||||
autocomplete={headerAutoCompleteList}
|
||||
collection={collection}
|
||||
/>
|
||||
@@ -140,8 +140,7 @@ const Headers = ({ collection, folder }) => {
|
||||
},
|
||||
header,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
)}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
autocomplete={MimeTypes}
|
||||
|
||||
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -14,7 +14,7 @@ const Wrapper = styled.div`
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
@@ -31,7 +31,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.btn-add-var {
|
||||
font-size: 0.8125rem;
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -113,7 +113,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<MultiLineEditor
|
||||
value={_var.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
@@ -126,8 +126,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
},
|
||||
_var,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
)}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
|
||||
@@ -8,18 +8,13 @@ import { useDispatch } from 'react-redux';
|
||||
const Vars = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const requestVars = folder.draft ? get(folder, 'draft.request.vars.req', []) : get(folder, 'root.request.vars.req', []);
|
||||
const responseVars = folder.draft ? get(folder, 'draft.request.vars.res', []) : get(folder, 'root.request.vars.res', []);
|
||||
const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<VarsTable folder={folder} collection={collection} vars={requestVars} varType="request" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<VarsTable folder={folder} collection={collection} vars={responseVars} varType="response" />
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
Save
|
||||
|
||||
@@ -28,8 +28,7 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
const activeHeadersCount = headers.filter((header) => header.enabled).length;
|
||||
|
||||
const requestVars = folderRoot?.request?.vars?.req || [];
|
||||
const responseVars = folderRoot?.request?.vars?.res || [];
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
|
||||
const activeVarsCount = requestVars.filter((v) => v.enabled).length;
|
||||
|
||||
const auth = get(folderRoot, 'request.auth.mode');
|
||||
const hasAuth = auth && auth !== 'none';
|
||||
@@ -101,7 +100,7 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
Docs
|
||||
</div>
|
||||
</div>
|
||||
<section className={`flex mt-4 h-full overflow-auto`}>{getTabPanel(tab)}</section>
|
||||
<section className="flex mt-4 h-full overflow-auto">{getTabPanel(tab)}</section>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user