Compare commits

..

55 Commits

Author SHA1 Message Date
Bijin A B
ae8851385f Merge branch 'main' of usebruno/bruno into workspaces 2025-12-04 04:17:45 +05:30
Bijin A B
6786f19d04 Merge pull request #6300 from usebruno/chore/oss-lint-fixes
chore: repo-wide lint fixes
2025-12-04 03:40:29 +05:30
Bijin A B
05fe8b1b27 chore: repo-wide lint fixes 2025-12-04 01:37:20 +05:30
sanish chirayath
b5722bf11c fix: wrap script in async IIFE to create isolated scope (#6229)
* fix: lexical scope for running scripts

* review fixes
2025-12-04 00:23:06 +05:30
Siddharth Gelera (reaper)
2b8da39bcf fix: update content security policy to allow inline scripts (#6139) 2025-12-03 19:57:10 +05:30
Pragadesh-45
38ba53be9f fix: stringify rawValue in brunoVarInfo (#6281) 2025-12-03 18:27:12 +05:30
Chirag Chandrashekhar
9159f523d9 Inbuilt Terminal (#6066)
* add: terminal

* added support for multiple terminal sessions and opening terminal from collection's working directory

* Use PowerShell as default shell on Windows

Replace cmd.exe with powershell.exe for terminal sessions on Windows.
Falls back to PWSH environment variable if set (for PowerShell Core).

* refactor(ui): improved session list by moving path display to hover for cleaner view

* chore: format

* refactor: improve terminal code quality and UI consistency

- Add terminal icon to 'Open in Terminal' dropdown item in CollectionItem
- Remove unused imports and functions (IconPlus, callIpc, canWriteToTerminal)
- Fix React key prop placement in SessionList component
- Replace deprecated substr with substring in terminal session ID generation
- Improve error handling for terminal cleanup on app quit
- Simplify terminal cleanup logic in window close handler

---------

Co-authored-by: naman-bruno <naman@usebruno.com>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-03 17:48:28 +05:30
Abhishek S Lal
a3d2d35d2e feat: introduce REQUEST_TYPES constant and update item deletion logic (#6244)
- Added REQUEST_TYPES constant to centralize request type definitions.
- Updated deleteItem action to filter items based on REQUEST_TYPES and folders.
- Modified collection.js to handle REQUEST_TYPES during file resequence operations.
2025-12-03 16:07:38 +05:30
Chirag Chandrashekhar
9caef9e573 Fix: WS variable interpolation (#6184)
* feat: add variable interpolation support for WebSocket requests

- Add WebSocket body interpolation in interpolateVars function
- Interpolate URL, headers, and all messages in request.body.ws array with full variable context
- Refactor sendWsRequest to use main process preparation (removes duplication)
- Add mode property to wsRequest object for proper request type detection
- Ensure consistent variable precedence matching HTTP/gRPC requests
- Centralize all interpolation logic in main process via prepareWsRequest

* Add Playwright tests for WebSocket variable interpolation

- Add tests for URL interpolation (wss://echo.{{url}}.org)
- Add tests for message content interpolation ({"test": "{{data}}"})
- Update test fixtures to use wss://echo.websocket.org echo server
- Add WEBSOCKET_FLOWS.md documentation
- Refactor queueWsMessage to handle variable interpolation in main process

* removed ws flow documentation

* chore: updated the network/index.js file to reduce merge conflicts by moving around code

* fix: added collection and item to WsQueryUrl Editor to fix available variable highlight

* chore: remove unnecessary whitespace in WebSocket event handlers

* feat: add automatic WebSocket reconnection on URL variable changes

- Detect changes to interpolated URL (including variable changes)
- Automatically disconnect and reconnect when interpolated URL changes
- Add debouncing (400ms) to prevent excessive reconnections
- Track previous interpolated URL to avoid unnecessary reconnects
- Store interpolated URL when connection becomes active
- Improve error handling and cleanup

* chore: removing diff

* Add WebSocket connection status IPC method

- Add connectionStatus() method to WsClient that returns detailed status
  ('disconnected', 'connecting', 'connected') instead of boolean
- Add renderer:ws:connection-status IPC handler in electron layer
- Add getWsConnectionStatus() utility function in network utils
- Provides more granular connection state information for UI components

* refactor: improve WebSocket connection status tracking in WsQueryUrl

- Replace boolean isConnectionActive with connectionStatus state ('disconnected', 'connecting', 'connected')
- Add useWsConnectionStatus hook to poll connection status every 2 seconds
- Refactor connection handlers: handleConnect, handleDisconnect, handleReconnect
- Update to use getWsConnectionStatus instead of isWsConnectionActive for more granular status
- Improve reconnect logic to handle URL variable interpolation changes
- Add proper connection status indicators in UI (connecting state with pulse animation)

* fix: improve WebSocket URL handling and request initialization

- Fix WebSocket URL state management by reading directly from item instead of local state
- Add handleUrlChange function to properly dispatch URL changes
- Fix interpolated URL change detection logic in useEffect
- Initialize params array for new WebSocket requests to prevent undefined errors
- Ensure params array is initialized when URL changes in draft/request
- Remove console.log statements and unused imports
- Update persistence test replacement URL to avoid port conflicts

These changes ensure WebSocket requests properly handle URL changes and
maintain consistent state between draft and saved requests.

* feat: refactor WebSocket connection status handling

---------

Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-03 15:52:52 +05:30
naman-bruno
77d2fecfe6 fixes 2025-12-03 15:02:45 +05:30
naman-bruno
663b06d60f fix blank line 2025-12-03 14:11:25 +05:30
naman-bruno
dc3b074520 fixes: coderabbit 2025-12-03 14:04:58 +05:30
Siddharth Gelera (reaper)
893058067d chore: improve coderabbit review instructions (#6282)
* chore: add more review items

* Update CODING_STANDARDS.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Sid <siddharth@usebruno.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-03 13:39:24 +05:30
naman-bruno
c7be4775b3 fixes: comments 2025-12-03 13:36:02 +05:30
Anoop M D
4a38f2d49f Update PR template to consent for AI usage 2025-12-03 03:49:02 +05:30
naman-bruno
72d5411df8 fixes 2025-12-03 00:48:19 +05:30
naman-bruno
d167be658f fix: close tests 2025-12-03 00:25:55 +05:30
naman-bruno
08c183b4ec fix: tests 2025-12-03 00:06:40 +05:30
Tarunkumar
d56e4f625b fixed min width issue so two windows can be used in parallel (#5863)
* fixed min width issue so two windows can be used in parallel

* updated the min height as well, so that Bruno can be viewed in quarter window on screen

---------

Co-authored-by: Tarunkumar N Bagmar <tarunkumar@Tarunkumars-MacBook-Air.local>
Co-authored-by: Anoop M D <anoop@usebruno.com>
2025-12-02 21:39:03 +05:30
Sid
9bbcf7ecbe fix:match to full string (#6272) 2025-12-02 17:54:53 +05:30
Chirag Chandrashekhar
ee4c923bc5 Merge pull request #6117 from chirag-bruno/bugfix/postman-export-omit-collection-vars
fix: Exporting Bruno collection as Postman collection omits collection variables
2025-12-02 16:47:15 +05:30
naman-bruno
bc82536a82 fix: ws and grpc request not saving (#6267) 2025-12-02 15:54:54 +05:30
Sid
b95ef99ef2 Merge pull request #6266 from usebruno/feat/copy-response-to-clipboard-5409
added copy button to copy response (#5409)
2025-12-02 15:47:18 +05:30
Pooja
2a251b1a62 added copy button to copy response (#6131)
* added copy button to copy response

* add: response action component

* fix: lint error

* add: playwright test

---------

Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
Co-authored-by: Sid <siddharth@usebruno.com>
2025-12-02 15:33:01 +05:30
Pooja
06a024a1d9 feat: add copy paste feature for folder (#6097)
* feat: add copy paste feature for folder

* add: playwright test

* fix

* fix

* add: keyboardFocusBg in light mode

* fix: copy paste in yaml collection

* improvement
2025-12-02 15:29:32 +05:30
Shashank Shekhar
8cee7bad39 added copy button to copy response (#5409)
Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com>
2025-12-02 14:53:51 +05:30
naman-bruno
c8d13f16c3 fixes 2025-12-02 14:42:00 +05:30
naman-bruno
399201bbc9 fixes 2025-12-02 14:41:51 +05:30
naman-bruno
93eae99302 init: workspaces 2025-12-02 14:41:39 +05:30
Chirag Chandrashekhar
bc4062b950 Bugfix/inaccurate process metrics (#6257) 2025-12-02 14:40:20 +05:30
Sid
b3ffc904ad feature/autoSave (#582) (#6211) 2025-12-02 14:32:34 +05:30
Sid
af707de684 fix(autoSave): minor bug fixes 2025-12-02 14:09:19 +05:30
Sid
be94224cfd Merge branch 'main' into feat/autosave-internal-582 2025-12-02 13:53:46 +05:30
Pooja
786a3414b8 remove: presets and response var (#6195) 2025-12-02 13:53:04 +05:30
Sid
7de56bd85c Merge branch 'main' into feat/autosave-internal-582 2025-12-02 13:52:12 +05:30
Pooja
4ce5debc4c feature/autoSave (#582) (#6225) 2025-12-02 13:46:06 +05:30
Pooja
8716e2b2a6 change: default cli sandbox to safe (#6198)
* change: default cli sandbox to safe
2025-12-02 13:09:16 +05:30
Sanjai Kumar
9d6486ba3e fix: update OAuth2 authorization header encoding to remove unnecessary URI encoding (#6263) 2025-12-02 12:56:08 +05:30
tlaloc911
dd72ee5d77 Add package directories to .gitignore (fix #6249) (#6250) 2025-12-01 09:23:44 +05:30
dependabot[bot]
7f204a8769 chore(deps): bump actions/upload-artifact from 4 to 5 (#5912)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-29 00:21:24 +05:30
dependabot[bot]
32990db3fb chore(deps): bump actions/checkout from 5 to 6 (#6192)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-29 00:18:20 +05:30
lohit
4f8d2c0c67 feat: default developer mode to nodevm and remove vm2 (#6187) 2025-11-29 00:04:55 +05:30
Anoop M D
8c06a229e9 Merge pull request #6238 from usebruno/chore/add-codemirror-placeholder
chore: added codemirror placeholder
2025-11-28 06:07:18 +05:30
Anoop M D
f367ea5a89 chore: added codemirror placeholder 2025-11-28 06:06:22 +05:30
Anoop M D
55a6af1ce3 Merge pull request #6236 from naman-bruno/bugfix/dropdown
rm: api spec from collection dropdown
2025-11-28 05:59:23 +05:30
Anoop M D
e9efcb48ac Merge pull request #6237 from usebruno/feat/design-revamp
feat: design revamp
2025-11-28 05:39:14 +05:30
Anoop M D
fa94efaa24 feat: design revamp 2025-11-28 05:10:29 +05:30
naman-bruno
1b2df9fba4 fix: dropdown 2025-11-28 02:09:48 +05:30
naman-bruno
7ee366eb81 Redesign dropdowns (#6235)
* redesign dropdowns

* fix: colors

---------

Co-authored-by: Anoop M D <anoop@usebruno.com>
2025-11-27 22:20:51 +05:30
Abhishek S Lal
59514127d5 Merge pull request #6171 from abhishek-bruno/style/update-font-size
style: updated font size to 13px using theme props.
2025-11-27 22:19:35 +05:30
Sanjai Kumar
9d98eb86c4 refactor: update deprecation messages for Presets and Post Response Vars (#6230)
* refactor: update DeprecationWarning component to accept children and enhance deprecation messages for Presets and Post Response Vars

* refactor: update DeprecationWarning component to use props for feature names and links, enhancing deprecation messages across various components
2025-11-27 21:57:39 +05:30
Sanjai Kumar
bb0096eb38 feat: added multipart data formatting in timeline (#6185)
refactor: remove escapeHeaderValue function and enhance formatMultipartData utility
2025-11-27 18:12:20 +05:30
Sanjai Kumar
6e88671788 feat: add support for legacy request object translations in Postman converter (#6174) 2025-11-27 10:42:06 +05:30
naman-bruno
d17048f80c feat(opencollection): add YAML-based collection support (#6155)
* add: opencollection

---------

Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
2025-11-27 00:55:08 +05:30
srikary12
086d0d98ef feature/autoSave (#582) 2025-11-26 12:17:52 +05:30
802 changed files with 24117 additions and 10369 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -34,6 +34,40 @@
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—dont 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.

View File

@@ -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

View File

@@ -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'
}
}
]));

391
package-lock.json generated
View File

@@ -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",
@@ -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",
@@ -8407,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",
@@ -8843,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
@@ -8851,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",
@@ -9137,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",
@@ -9209,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"
@@ -9242,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"
@@ -9306,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",
@@ -19413,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",
@@ -21241,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",
@@ -23735,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",
@@ -26844,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",
@@ -28437,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",
@@ -28527,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",
@@ -30233,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",
@@ -30274,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",
@@ -30282,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",
@@ -31805,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",
@@ -31852,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",
@@ -31874,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",
@@ -31954,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",
@@ -32040,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": {
@@ -32056,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",
@@ -32242,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": {

View File

@@ -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",

View File

@@ -6,4 +6,4 @@ module.exports = {
}]
],
plugins: ['babel-plugin-styled-components']
};
};

View File

@@ -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)'
]
};
};

View File

@@ -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",

View File

@@ -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',

View File

@@ -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',

View File

@@ -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">

View File

@@ -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;

View File

@@ -15,6 +15,7 @@ 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');
@@ -37,7 +38,8 @@ export default class CodeEditor extends React.Component {
this.lintOptions = {
esversion: 11,
expr: true,
asi: true
asi: true,
highlightLines: true
};
this.state = {
@@ -50,6 +52,7 @@ 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,
@@ -64,7 +67,7 @@ export default class CodeEditor extends React.Component {
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',
@@ -102,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');
@@ -148,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'
@@ -185,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);
@@ -194,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,
@@ -207,6 +210,9 @@ export default class CodeEditor extends React.Component {
);
setupLinkAware(editor);
// Setup lint error tooltip on line number hover
this.cleanupLintErrorTooltip = setupLintErrorTooltip(editor);
}
}
@@ -272,6 +278,10 @@ export default class CodeEditor extends React.Component {
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;
}
}

View File

@@ -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', () => {});
});

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 (

View File

@@ -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;

View File

@@ -87,7 +87,7 @@ const AuthMode = ({ collection }) => {
}}
>
NTLM Auth
</div>
</div>
<div
className="dropdown-item"
onClick={() => {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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">

View File

@@ -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;

View File

@@ -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 = () => {

View File

@@ -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 {

View File

@@ -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} />;
}

View File

@@ -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">

View File

@@ -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.

View File

@@ -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'] {

View File

@@ -123,8 +123,7 @@ const Headers = ({ collection }) => {
},
header,
'name'
)
}
)}
autocomplete={headerAutoCompleteList}
collection={collection}
/>
@@ -143,8 +142,7 @@ const Headers = ({ collection }) => {
},
header,
'value'
)
}
)}
collection={collection}
autocomplete={MimeTypes}
/>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,123 +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';
import DeprecationWarning from 'components/DeprecationWarning';
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">
<DeprecationWarning featureName="Presets" learnMoreUrl="https://github.com/usebruno/bruno/discussions/6234" />
<div className="text-xs mb-4 mt-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;

View File

@@ -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" />}

View File

@@ -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;

View File

@@ -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'] {

View File

@@ -127,8 +127,7 @@ const VarsTable = ({ collection, vars, varType }) => {
},
_var,
'value'
)
}
)}
collection={collection}
/>
</td>

View File

@@ -9,20 +9,13 @@ 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>
<DeprecationWarning featureName="Post Response Variables" learnMoreUrl="https://github.com/usebruno/bruno/discussions/6231" />
<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

View File

@@ -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 />}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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;
}
}
}
`;

View File

@@ -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">

View File

@@ -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">

View File

@@ -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;

View File

@@ -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 });

View File

@@ -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

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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' });
}

View File

@@ -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}`}>

View File

@@ -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>
))}

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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={() => {

View File

@@ -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'] {

View File

@@ -121,8 +121,7 @@ const Headers = ({ collection, folder }) => {
},
header,
'name'
)
}
)}
autocomplete={headerAutoCompleteList}
collection={collection}
/>
@@ -141,8 +140,7 @@ const Headers = ({ collection, folder }) => {
},
header,
'value'
)
}
)}
collection={collection}
item={folder}
autocomplete={MimeTypes}

View File

@@ -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'] {

View File

@@ -126,8 +126,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
},
_var,
'value'
)
}
)}
collection={collection}
item={folder}
/>

View File

@@ -4,25 +4,17 @@ import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import DeprecationWarning from 'components/DeprecationWarning';
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>
<DeprecationWarning featureName="Post Response Variables" learnMoreUrl="https://github.com/usebruno/bruno/discussions/6231" />
<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

View File

@@ -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>
);

View File

@@ -46,10 +46,10 @@ const CopyEnvironment = ({ environment, onClose }) => {
return (
<Portal>
<Modal size="sm" title={'Copy Global Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
<Modal size="sm" title="Copy Global Environment" confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="name" className="block font-semibold">
<label htmlFor="name" className="block font-medium">
New Environment Name
</label>
<input

View File

@@ -27,7 +27,7 @@ const CreateEnvironment = ({ 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) });
})
@@ -62,14 +62,14 @@ const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
<Portal>
<Modal
size="sm"
title={'Create Global Environment'}
title="Create Global 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">

View File

@@ -22,12 +22,12 @@ const DeleteEnvironment = ({ onClose, environment }) => {
<StyledWrapper>
<Modal
size="sm"
title={'Delete Global Environment'}
title="Delete Global 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>

View File

@@ -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>

Some files were not shown because too many files have changed in this diff Show More